ezstream.c 20 KB
Newer Older
1
2
3
/*
 *  ezstream - source client for Icecast with external en-/decoder support
 *  Copyright (C) 2003, 2004, 2005, 2006  Ed Zaleski <oddsock@oddsock.org>
4
 *  Copyright (C) 2007, 2009, 2015, 2017  Moritz Grimm <mgrimm@mrsserver.net>
5
6
 *
 *  This program is free software; you can redistribute it and/or modify
moritz's avatar
moritz committed
7
8
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
9
10
11
12
13
14
15
16
17
18
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
oddsock's avatar
oddsock committed
19

20
21
#include "compat.h"

22
23
#include "ezstream.h"

24
#include <signal.h>
25

26
#include "cfg.h"
27
#include "cmdline.h"
28
#include "log.h"
29
#include "metadata.h"
30
#include "playlist.h"
31
#include "stream.h"
32
#include "util.h"
33
#include "xalloc.h"
34

35
36
37
38
#define STREAM_DONE	0
#define STREAM_CONT	1
#define STREAM_SKIP	2
#define STREAM_SERVERR	3
39
#define STREAM_UPDMDATA 4
40

41
42
43
playlist_t		 playlist;
int			 playlistMode;
unsigned int		 resource_errors;
44

45
46
47
const int		 ezstream_signals[] = {
	SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2
};
48

49
50
51
52
53
volatile sig_atomic_t	 rereadPlaylist;
volatile sig_atomic_t	 rereadPlaylist_notify;
volatile sig_atomic_t	 skipTrack;
volatile sig_atomic_t	 queryMetadata;
volatile sig_atomic_t	 quit;
oddsock's avatar
oddsock committed
54

55
void		sig_handler(int);
56
57
58
char *		buildReencodeCommand(const char *, const char *, metadata_t);
metadata_t	getMetadata(const char *);
FILE *		openResource(stream_t, const char *, int *, metadata_t *,
59
			     int *, long *);
60
int		reconnect(stream_t);
61
const char *	getTimeString(long);
62
int		sendStream(stream_t, FILE *, const char *, int, const char *,
63
			   struct timespec *);
64
65
int		streamFile(stream_t, const char *);
int		streamPlaylist(stream_t);
66
int		ez_shutdown(int);
oddsock's avatar
oddsock committed
67

68
69
70
71
void
sig_handler(int sig)
{
	switch (sig) {
72
73
74
75
	case SIGTERM:
	case SIGINT:
		quit = 1;
		break;
76
77
78
79
80
81
82
	case SIGHUP:
		rereadPlaylist = 1;
		rereadPlaylist_notify = 1;
		break;
	case SIGUSR1:
		skipTrack = 1;
		break;
83
84
85
	case SIGUSR2:
		queryMetadata = 1;
		break;
86
87
88
89
90
	default:
		break;
	}
}

91
char *
92
buildReencodeCommand(const char *extension, const char *fileName,
93
    metadata_t mdata)
94
{
95
96
97
98
99
	cfg_decoder_t	 decoder;
	cfg_encoder_t	 encoder;
	char		*dec_str, *enc_str;
	char		*commandString;
	size_t		 commandStringLen;
100
	char		*localTitle, *localArtist, *localMetaString, *localAlbum;
101
102
103
104

	decoder = cfg_decoder_find(extension);
	if (!decoder) {
		log_error("cannot decode: %s: unsupported file extension %s",
105
		    fileName, extension);
106
		return (NULL);
107
	}
108
109
110
111
112
113
114
	encoder = cfg_encoder_get(cfg_get_stream_encoder());
	if (!encoder) {
		log_error("cannot encode: %s: unknown encoder",
		    cfg_get_stream_encoder());
		return (NULL);
	}

115
116
117
118
	localTitle = util_utf82char(metadata_get_title(mdata), ICONV_REPLACE);
	localArtist = util_utf82char(metadata_get_artist(mdata), ICONV_REPLACE);
	localAlbum = util_utf82char(metadata_get_album(mdata), ICONV_REPLACE);
	localMetaString = util_utf82char(metadata_get_string(mdata),
119
120
	    ICONV_REPLACE);

121
	dec_str = util_replacestring(cfg_decoder_get_program(decoder),
122
123
	    PLACEHOLDER_TRACK, fileName);
	if (strstr(dec_str, PLACEHOLDER_ARTIST) != NULL) {
124
		char *tmpStr = util_replacestring(dec_str, PLACEHOLDER_ARTIST,
125
		    localArtist);
126
127
		xfree(dec_str);
		dec_str = tmpStr;
128
	}
129
	if (strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
130
		char *tmpStr = util_replacestring(dec_str, PLACEHOLDER_TITLE,
131
		    localTitle);
132
133
		xfree(dec_str);
		dec_str = tmpStr;
134
	}
135
	if (strstr(dec_str, PLACEHOLDER_ALBUM) != NULL) {
136
		char *tmpStr = util_replacestring(dec_str, PLACEHOLDER_ALBUM,
137
138
139
140
		    localAlbum);
		xfree(dec_str);
		dec_str = tmpStr;
	}
141
142
143
144
145
146
147
148
149
150
	/*
	 * if meta
	 *   if (prog && format)
	 *      metatoformat
	 *   else
	 *     if (!prog && title)
	 *       emptymeta
	 *     else
	 *       replacemeta
	 */
151
152
153
	if (strstr(dec_str, PLACEHOLDER_METADATA) != NULL) {
		if (cfg_get_metadata_program() &&
		    cfg_get_metadata_format_str()) {
154
155
			char *mdataString = metadata_format_string(mdata,
			    cfg_get_metadata_format_str());
156
			char *tmpStr = util_replacestring(dec_str,
157
158
			    PLACEHOLDER_METADATA, mdataString);
			xfree(dec_str);
159
			xfree(mdataString);
160
			dec_str = tmpStr;
161
		} else {
162
163
			if (!cfg_get_metadata_program() &&
			    strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
164
				char *tmpStr = util_replacestring(dec_str,
165
166
167
				    PLACEHOLDER_METADATA, "");
				xfree(dec_str);
				dec_str = tmpStr;
168
			} else {
169
				char *tmpStr = util_replacestring(dec_str,
170
171
172
				    PLACEHOLDER_METADATA, localMetaString);
				xfree(dec_str);
				dec_str = tmpStr;
173
174
175
			}
		}
	}
176

177
178
179
	if (!cfg_encoder_get_program(encoder))
		return (dec_str);

180
	enc_str = util_replacestring(cfg_encoder_get_program(encoder),
181
182
	    PLACEHOLDER_ARTIST, localArtist);
	if (strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
183
		char *tmpStr = util_replacestring(enc_str, PLACEHOLDER_TITLE,
184
		    localTitle);
185
186
		xfree(enc_str);
		enc_str = tmpStr;
187
	}
188
	if (strstr(enc_str, PLACEHOLDER_ALBUM) != NULL) {
189
		char *tmpStr = util_replacestring(enc_str, PLACEHOLDER_ALBUM,
190
191
192
193
		    localAlbum);
		xfree(enc_str);
		enc_str = tmpStr;
	}
194
195
196
	if (strstr(enc_str, PLACEHOLDER_METADATA) != NULL) {
		if (cfg_get_metadata_program() &&
		    cfg_get_metadata_format_str()) {
197
198
			char *mdataString = metadata_format_string(mdata,
			    cfg_get_metadata_format_str());
199
			char *tmpStr = util_replacestring(enc_str,
200
201
			    PLACEHOLDER_METADATA, mdataString);
			xfree(enc_str);
202
			xfree(mdataString);
203
			enc_str = tmpStr;
204
		} else {
205
206
			if (!cfg_get_metadata_program() &&
			    strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
207
				char *tmpStr = util_replacestring(enc_str,
208
209
210
				    PLACEHOLDER_METADATA, "");
				xfree(enc_str);
				enc_str = tmpStr;
211
			} else {
212
				char *tmpStr = util_replacestring(enc_str,
213
214
215
				    PLACEHOLDER_METADATA, localMetaString);
				xfree(enc_str);
				enc_str = tmpStr;
216
217
218
			}
		}
	}
219

220
221
	commandStringLen = strlen(dec_str) + strlen(" | ") +
	    strlen(enc_str) + 1;
222
	commandString = xcalloc(commandStringLen, sizeof(char));
223
224
	snprintf(commandString, commandStringLen, "%s | %s", dec_str,
	    enc_str);
225

226
227
228
	xfree(localTitle);
	xfree(localArtist);
	xfree(localMetaString);
229
230
	xfree(dec_str);
	xfree(enc_str);
231
232

	return (commandString);
233
}
oddsock's avatar
oddsock committed
234

235
metadata_t
236
getMetadata(const char *fileName)
moritz's avatar
moritz committed
237
{
238
	metadata_t	mdata;
239

240
241
242
	if (cfg_get_metadata_program()) {
		if (NULL == (mdata = metadata_program(fileName,
		    cfg_get_metadata_normalize_strings())))
243
244
			return (NULL);

245
		if (!metadata_program_update(mdata, METADATA_ALL)) {
246
			metadata_free(&mdata);
247
			return (NULL);
248
249
		}
	} else {
250
251
		if (NULL == (mdata = metadata_file(fileName,
		    cfg_get_metadata_normalize_strings())))
252
			return (NULL);
moritz's avatar
moritz committed
253

254
255
		if (!metadata_file_update(mdata)) {
			metadata_free(&mdata);
256
			return (NULL);
257
		}
258
	}
259

260
261
262
	return (mdata);
}

263
FILE *
264
265
openResource(stream_t stream, const char *fileName, int *popenFlag,
	     metadata_t *mdata_p, int *isStdin, long *songLen)
266
{
267
268
269
270
	FILE		*filep = NULL;
	char		 extension[25];
	char		*p = NULL;
	char		*pCommandString = NULL;
271
	metadata_t	 mdata;
oddsock's avatar
oddsock committed
272

273
274
	if (mdata_p != NULL)
		*mdata_p = NULL;
275
276
	if (songLen != NULL)
		*songLen = 0;
277

278
	if (strcmp(fileName, "stdin") == 0) {
279
280
		if (cfg_get_metadata_program()) {
			if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
281
				return (NULL);
282
			if (0 > stream_set_metadata(stream, mdata, NULL)) {
283
284
285
				metadata_free(&mdata);
				return (NULL);
			}
286
287
288
289
			if (mdata_p != NULL)
				*mdata_p = mdata;
			else
				metadata_free(&mdata);
290
		}
291

292
293
		if (isStdin != NULL)
			*isStdin = 1;
294
		filep = stdin;
295
		return (filep);
oddsock's avatar
oddsock committed
296
	}
297

298
299
300
301
	if (isStdin != NULL)
		*isStdin = 0;

	extension[0] = '\0';
302
303
304
305
306
307
308
	p = strrchr(fileName, '.');
	if (p != NULL)
		strlcpy(extension, p, sizeof(extension));
	for (p = extension; *p != '\0'; p++)
		*p = tolower((int)*p);

	if (strlen(extension) == 0) {
309
		log_error("%s: cannot determine file type", fileName);
310
311
312
		return (filep);
	}

313
314
	if (cfg_get_metadata_program()) {
		if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
315
316
317
318
319
			return (NULL);
	} else {
		if ((mdata = getMetadata(fileName)) == NULL)
			return (NULL);
	}
320
321
	if (songLen != NULL)
		*songLen = metadata_get_length(mdata);
322
323

	*popenFlag = 0;
324
	if (cfg_get_stream_encoder()) {
325
		int	stderr_fd = -1;
326

327
328
		pCommandString = buildReencodeCommand(extension, fileName,
		    mdata);
329
330
331
332
		if (mdata_p != NULL)
			*mdata_p = mdata;
		else
			metadata_free(&mdata);
333
		log_info("running command: %s", pCommandString);
334

335
		if (cfg_get_program_quiet_stderr()) {
336
337
			int fd;

338
			stderr_fd = dup(fileno(stderr));
339
			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
340
341
				log_alert("%s: %s", _PATH_DEVNULL,
				    strerror(errno));
342
				exit(1);
343
344
			}

345
346
347
348
349
350
351
352
353
354
			dup2(fd, fileno(stderr));
			if (fd > 2)
				close(fd);
		}

		fflush(NULL);
		errno = 0;
		if ((filep = popen(pCommandString, "r")) == NULL) {
			/* popen() does not set errno reliably ... */
			if (errno)
355
356
				log_error("execution error: %s: %s",
				    pCommandString, strerror(errno));
357
			else
358
359
				log_error("execution error: %s",
				    pCommandString);
360
361
362
363
		} else {
			*popenFlag = 1;
		}
		xfree(pCommandString);
364

365
		if (cfg_get_program_quiet_stderr())
366
			dup2(stderr_fd, fileno(stderr));
367
368
369

		if (stderr_fd > 2)
			close(stderr_fd);
370

371
		return (filep);
372
	}
373

374
375
376
377
	if (mdata_p != NULL)
		*mdata_p = mdata;
	else
		metadata_free(&mdata);
378

379
380
381
382
	if ((filep = fopen(fileName, "rb")) == NULL) {
		log_error("%s: %s", fileName, strerror(errno));
		return (NULL);
	}
383
384

	return (filep);
385
386
}

387
int
388
reconnect(stream_t stream)
389
390
391
392
393
{
	unsigned int	i;

	i = 0;
	while (++i) {
394
		if (cfg_get_server_reconnect_attempts() > 0)
395
			log_notice("reconnect: %s: attempt #%u/%u ...",
396
397
			    cfg_get_server_hostname(), i,
			    cfg_get_server_reconnect_attempts());
398
		else
399
			log_notice("reconnect: %s: attempt #%u ...",
400
			    cfg_get_server_hostname(), i);
401

402
403
		stream_disconnect(stream);
		if (0 == stream_connect(stream)) {
404
			log_notice("reconnect: %s: success",
405
			    cfg_get_server_hostname());
406
			return (0);
407
408
		}

409
410
		if (cfg_get_server_reconnect_attempts() > 0 &&
		    i >= cfg_get_server_reconnect_attempts())
411
412
			break;

413
		if (quit)
414
			return (-1);
415
416
		else
			sleep(5);
417
418
	};

419
	log_warning("reconnect failed: giving up");
420
421

	return (-1);
422
423
}

424
const char *
425
getTimeString(long seconds)
426
427
{
	static char	str[20];
428
	long		secs, mins, hours;
429
430
431
432
433
434
435
436
437
438

	if (seconds < 0)
		return (NULL);

	secs = seconds;
	hours = secs / 3600;
	secs %= 3600;
	mins = secs / 60;
	secs %= 60;

439
	snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
440
441
442
	return ((const char *)str);
}

443
int
444
sendStream(stream_t stream, FILE *filepstream, const char *fileName,
445
	   int isStdin, const char *songLenStr, struct timespec *tv)
446
{
447
	char		  buff[4096];
448
449
450
451
452
	size_t		  bytes_read, total, oldTotal;
	int		  ret;
	double		  kbps = -1.0;
	struct timespec	  timeStamp, *startTime = tv;
	struct timespec	  callTime, currentTime;
453

454
	clock_gettime(CLOCK_MONOTONIC, &callTime);
455

456
	timeStamp.tv_sec = startTime->tv_sec;
457
	timeStamp.tv_nsec = startTime->tv_nsec;
458
459

	total = oldTotal = 0;
460
	ret = STREAM_DONE;
461
462
463
464
465
466
467
468
	while ((bytes_read = fread(buff, 1, sizeof(buff), filepstream)) > 0) {
		if (!stream_get_connected(stream)) {
			log_warning("%s: connection lost",
			    cfg_get_server_hostname());
			if (0 > reconnect(stream)) {
				ret = STREAM_SERVERR;
				break;
			}
469
470
		}

471
		stream_sync(stream);
472

473
474
		if (0 > stream_send(stream, buff, bytes_read)) {
			if (0 > reconnect(stream))
475
				ret = STREAM_SERVERR;
476
			break;
477
478
		}

479
480
		if (quit)
			break;
481
482
		if (rereadPlaylist_notify) {
			rereadPlaylist_notify = 0;
483
			if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
484
				log_notice("HUP signal received: playlist re-read scheduled");
485
486
487
		}
		if (skipTrack) {
			skipTrack = 0;
488
			ret = STREAM_SKIP;
489
490
			break;
		}
491

492
		clock_gettime(CLOCK_MONOTONIC, &currentTime);
493
494

		if (queryMetadata ||
495
		    (0 <= cfg_get_metadata_refresh_interval() &&
496
497
			(currentTime.tv_sec - callTime.tv_sec >=
			    cfg_get_metadata_refresh_interval()))) {
498
			queryMetadata = 0;
499
			if (cfg_get_metadata_program()) {
500
				ret = STREAM_UPDMDATA;
501
				break;
oddsock's avatar
oddsock committed
502
			}
503
504
		}

505
		total += bytes_read;
506
		if (cfg_get_program_rtstatus_output()) {
507
			double	oldTime, newTime;
508

509
			if (!isStdin && playlistMode) {
510
511
				if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
					char *tmp = xstrdup(cfg_get_media_filename());
512
					printf("  [%s]", basename(tmp));
513
514
515
					xfree(tmp);
				} else
					printf("  [%4lu/%-4lu]",
516
517
					    playlist_get_position(playlist),
					    playlist_get_num_items(playlist));
518
			}
519
520

			oldTime = (double)timeStamp.tv_sec
521
			    + (double)timeStamp.tv_nsec / 1000000000.0;
522
			newTime = (double)currentTime.tv_sec
523
			    + (double)currentTime.tv_nsec / 1000000000.0;
524
525
			if (songLenStr == NULL)
				printf("  [ %s]",
526
527
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec));
528
529
			else
				printf("  [ %s/%s]",
530
531
532
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec),
				    songLenStr);
533
			if (newTime - oldTime >= 1.0) {
534
				kbps = (((double)(total - oldTotal)
535
				    / (newTime - oldTime)) * 8.0) / 1000.0;
536
				timeStamp.tv_sec = currentTime.tv_sec;
537
				timeStamp.tv_nsec = currentTime.tv_nsec;
538
539
540
541
542
543
544
545
546
547
				oldTotal = total;
			}
			if (kbps < 0)
				printf("                 ");
			else
				printf("  [%8.2f kbps]", kbps);

			printf("  \r");
			fflush(stdout);
		}
oddsock's avatar
oddsock committed
548
	}
549
550
551
	if (ferror(filepstream)) {
		if (errno == EINTR) {
			clearerr(filepstream);
552
			ret = STREAM_CONT;
553
		} else if (errno == EBADF && isStdin)
554
			log_notice("no (more) data available on standard input");
555
		else
556
557
			log_error("sendStream: %s: %s", fileName,
			    strerror(errno));
558
559
	}

560
	return (ret);
561
562
563
}

int
564
streamFile(stream_t stream, const char *fileName)
565
566
567
{
	FILE		*filepstream = NULL;
	int		 popenFlag = 0;
568
	char		*songLenStr = NULL;
569
	int		 isStdin = 0;
570
571
	int		 ret, retval = 0;
	long		 songLen;
572
	metadata_t	 mdata;
573
	struct timespec	 startTime;
574

575
	if ((filepstream = openResource(stream, fileName, &popenFlag, &mdata, &isStdin, &songLen))
576
577
	    == NULL) {
		if (++resource_errors > 100) {
578
			log_error("too many errors; giving up");
579
580
			return (0);
		}
581
582
		/* Continue with next resource on failure: */
		return (1);
583
584
	}
	resource_errors = 0;
585

586
	if (mdata != NULL) {
587
		char	*tmp, *metaData;
588

589
		tmp = metadata_assemble_string(mdata);
590
		if ((metaData = util_utf82char(tmp, ICONV_REPLACE)) == NULL)
591
			metaData = xstrdup("(unknown title)");
592
		xfree(tmp);
593
		log_notice("streaming: %s (%s)", metaData, fileName);
594
		xfree(metaData);
595
596

		/* MP3 streams are special, so set the metadata explicitly: */
597
		if (CFG_STREAM_MP3 == cfg_get_stream_format())
598
			stream_set_metadata(stream, mdata, NULL);
599
600

		metadata_free(&mdata);
601
	} else if (isStdin)
602
		log_notice("streaming: standard input");
603

moritz's avatar
moritz committed
604
	if (songLen > 0)
605
		songLenStr = xstrdup(getTimeString(songLen));
606
	clock_gettime(CLOCK_MONOTONIC, &startTime);
607
	do {
608
		ret = sendStream(stream, filepstream, fileName, isStdin,
609
		    songLenStr, &startTime);
610
611
		if (quit)
			break;
612
		if (ret != STREAM_DONE) {
613
614
			if ((skipTrack && rereadPlaylist) ||
			    (skipTrack && queryMetadata)) {
615
				skipTrack = 0;
616
617
618
619
620
				ret = STREAM_CONT;
			}
			if (queryMetadata && rereadPlaylist) {
				queryMetadata = 0;
				ret = STREAM_CONT;
621
			}
622
			if (ret == STREAM_SKIP || skipTrack) {
623
				skipTrack = 0;
624
625
				if (!isStdin)
					log_notice("USR1 signal received: skipping current track");
626
				retval = 1;
627
628
				ret = STREAM_DONE;
			}
629
630
			if (ret == STREAM_UPDMDATA || queryMetadata) {
				queryMetadata = 0;
631
				if (cfg_get_metadata_no_updates())
632
					continue;
633
				if (cfg_get_metadata_program()) {
634
					char		*mdataStr = NULL;
635
					metadata_t	 prog_mdata;
636

637
					log_info("running metadata program: %s",
638
639
					    cfg_get_metadata_program());
					if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
640
641
642
643
						retval = 0;
						ret = STREAM_DONE;
						continue;
					}
644
					if (0> stream_set_metadata(stream, prog_mdata, &mdataStr)) {
645
646
						retval = 0;
						ret = STREAM_DONE;
647
						continue;
648
					}
649
					metadata_free(&prog_mdata);
650
					log_info("new metadata: %s", mdataStr);
651
652
653
					xfree(mdataStr);
				}
			}
654
655
656
			if (ret == STREAM_SERVERR) {
				retval = 0;
				ret = STREAM_DONE;
657
658
659
			}
		} else
			retval = 1;
660
	} while (ret != STREAM_DONE);
661
662

	if (popenFlag)
oddsock's avatar
oddsock committed
663
		pclose(filepstream);
Moritz Grimm's avatar
Moritz Grimm committed
664
	else if (!isStdin)
oddsock's avatar
oddsock committed
665
		fclose(filepstream);
666

667
668
669
	if (songLenStr != NULL)
		xfree(songLenStr);

670
	return (retval);
oddsock's avatar
oddsock committed
671
}
672

673
int
674
streamPlaylist(stream_t stream)
675
676
{
	const char	*song;
moritz's avatar
moritz committed
677
	char		 lastSong[PATH_MAX];
678
679

	if (playlist == NULL) {
680
681
		switch (cfg_get_media_type()) {
		case CFG_MEDIA_PROGRAM:
682
			if ((playlist = playlist_program(cfg_get_media_filename())) == NULL)
683
				return (0);
684
685
686
687
688
689
			break;
		case CFG_MEDIA_STDIN:
			if ((playlist = playlist_read(NULL)) == NULL)
				return (0);
			break;
		default:
690
			if ((playlist = playlist_read(cfg_get_media_filename())) == NULL)
691
				return (0);
692
			if (playlist_get_num_items(playlist) == 0)
693
				log_warning("%s: playlist empty",
694
				    cfg_get_media_filename());
695
			break;
696
		}
697
	} else {
698
699
700
701
702
		/*
		 * XXX: This preserves traditional behavior, however,
		 *      rereading the playlist after each walkthrough seems a
		 *      bit more logical.
		 */
703
		playlist_rewind(playlist);
704
	}
705

706
707
	if (CFG_MEDIA_PROGRAM != cfg_get_media_type() &&
	    cfg_get_media_shuffle())
708
709
710
711
		playlist_shuffle(playlist);

	while ((song = playlist_get_next(playlist)) != NULL) {
		strlcpy(lastSong, song, sizeof(lastSong));
712
		if (!streamFile(stream, song))
713
			return (0);
714
715
		if (quit)
			break;
716
717
		if (rereadPlaylist) {
			rereadPlaylist = rereadPlaylist_notify = 0;
718
			if (CFG_MEDIA_PROGRAM == cfg_get_media_type())
719
				continue;
720
			log_notice("rereading playlist");
721
722
			if (!playlist_reread(&playlist))
				return (0);
723
			if (cfg_get_media_shuffle())
724
725
726
727
				playlist_shuffle(playlist);
			else {
				playlist_goto_entry(playlist, lastSong);
				playlist_skip_next(playlist);
oddsock's avatar
oddsock committed
728
			}
729
			continue;
730
		}
oddsock's avatar
oddsock committed
731
	}
732

733
	return (1);
oddsock's avatar
oddsock committed
734
735
}

736
int
737
ez_shutdown(int exitval)
738
{
739
	stream_exit();
740
741
742
	playlist_exit();
	cfg_encoder_exit();
	cfg_decoder_exit();
Moritz Grimm's avatar
Moritz Grimm committed
743
	log_exit();
744
	cfg_exit();
745
746
747
748

	return (exitval);
}

749
int
750
main(int argc, char *argv[])
oddsock's avatar
oddsock committed
751
{
752
	int		 ret, cont;
753
	const char	*errstr;
754
	stream_t	 stream;
755
756
	extern char	*optarg;
	extern int	 optind;
757
	struct sigaction act;
moritz's avatar
moritz committed
758
	unsigned int	 i;
759

Moritz Grimm's avatar
Moritz Grimm committed
760
	ret = 1;
761
762
	if (0 > cfg_init() ||
	    0 > cmdline_parse(argc, argv, &ret) ||
763
764
765
766
	    0 > log_init() ||
	    0 > cfg_decoder_init() ||
	    0 > cfg_encoder_init() ||
	    0 > playlist_init() ||
767
	    0 > cfg_file_reload() ||
768
	    0 > stream_init())
Moritz Grimm's avatar
Moritz Grimm committed
769
		return (ez_shutdown(ret));
770

771
772
	if (0 > cfg_check(&errstr)) {
		log_error("%s: %s", cfg_get_program_config_file(), errstr);
773
		return (ez_shutdown(2));
oddsock's avatar
oddsock committed
774
	}
775

776
777
	stream = stream_get(STREAM_DEFAULT);
	if (0 > stream_setup(stream))
778
		return (ez_shutdown(1));
oddsock's avatar
oddsock committed
779

780
781
	memset(&act, 0, sizeof(act));
	act.sa_handler = sig_handler;
782
#ifdef SA_RESTART
783
	act.sa_flags = SA_RESTART;
784
#endif
moritz's avatar
moritz committed
785
786
	for (i = 0; i < sizeof(ezstream_signals) / sizeof(int); i++) {
		if (sigaction(ezstream_signals[i], &act, NULL) == -1) {
787
			log_syserr(ERROR, errno, "sigaction");
788
			return (ez_shutdown(1));
moritz's avatar
moritz committed
789
790
		}
	}
791
792
793
794
	/*
	 * Ignore SIGPIPE, which has been seen to give a long-running ezstream
	 * process trouble. EOF and/or EPIPE are also easier to handle.
	 */
795
796
797
#ifndef SIG_IGN
# define SIG_IGN	 (void (*)(int))1
#endif /* !SIG_IGN */
798
799
	act.sa_handler = SIG_IGN;
	if (sigaction(SIGPIPE, &act, NULL) == -1) {
800
		log_syserr(ERROR, errno, "sigaction");
801
802
		return (ez_shutdown(1));
	}
803

804
	if (0 > util_write_pid_file(cfg_get_program_pid_file()))
805
806
		log_syserr(WARNING, errno, cfg_get_program_pid_file());

807
808
809
810
811
812