ezstream.c 19.6 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 "mdata.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
stream_t		 main_stream;
42
43
44
playlist_t		 playlist;
int			 playlistMode;
unsigned int		 resource_errors;
45

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

50
51
52
53
54
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
55

56
void		sig_handler(int);
57
58
59
60

static char *	_build_reencode_cmd(const char *, const char *, cfg_stream_t,
				    mdata_t);
static FILE *	openResource(stream_t, const char *, int *, mdata_t *,
61
			     int *, long *);
62
int		reconnect(stream_t);
63
const char *	getTimeString(long);
64
int		sendStream(stream_t, FILE *, const char *, int, const char *,
65
			   struct timespec *);
66
67
int		streamFile(stream_t, const char *);
int		streamPlaylist(stream_t);
68
int		ez_shutdown(int);
oddsock's avatar
oddsock committed
69

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

93
static char *
94
_build_reencode_cmd(const char *extension, const char *filename,
95
    cfg_stream_t cfg_stream, mdata_t md)
96
{
Moritz Grimm's avatar
Moritz Grimm committed
97
98
	cfg_decoder_t		 decoder;
	cfg_encoder_t		 encoder;
99
	char			*artist, *album, *title, *songinfo, *tmp;
Moritz Grimm's avatar
Moritz Grimm committed
100
101
102
103
104
105
106
	char			*filename_quoted;
	char			*custom_songinfo;
	struct util_dict	 dicts[6];
	char			*dec_str;
	char			*enc_str;
	char			*cmd_str;
	size_t			 cmd_str_size;
107

108
	decoder = cfg_decoder_list_findext(cfg_get_decoders(), extension);
109
110
	if (!decoder) {
		log_error("cannot decode: %s: unsupported file extension %s",
111
		    filename, extension);
112
		return (NULL);
113
	}
114
115
	encoder = cfg_encoder_list_get(cfg_get_encoders(),
	    cfg_stream_get_encoder(cfg_stream));
116
117
	if (!encoder) {
		log_error("cannot encode: %s: unknown encoder",
118
		    cfg_stream_get_encoder(cfg_stream));
119
120
121
		return (NULL);
	}

122
	tmp = util_utf82char(mdata_get_artist(md));
123
	artist = util_shellquote(tmp, 0);
124
125
126
	xfree(tmp);

	tmp = util_utf82char(mdata_get_album(md));
127
	album = util_shellquote(tmp, 0);
128
129
130
	xfree(tmp);

	tmp = util_utf82char(mdata_get_title(md));
131
	title = util_shellquote(tmp, 0);
132
133
134
	xfree(tmp);

	tmp = util_utf82char(mdata_get_songinfo(md));
135
	songinfo = util_shellquote(tmp, 0);
136
	xfree(tmp);
Moritz Grimm's avatar
Moritz Grimm committed
137

138
	filename_quoted = util_shellquote(filename, 0);
Moritz Grimm's avatar
Moritz Grimm committed
139

140
	/*
Moritz Grimm's avatar
Moritz Grimm committed
141
142
143
144
145
	 * if (prog && format)
	 *    metatoformat
	 * else
	 *   if (!prog && title)
	 *     emptymeta
146
	 *   else
Moritz Grimm's avatar
Moritz Grimm committed
147
	 *     replacemeta
148
	 */
Moritz Grimm's avatar
Moritz Grimm committed
149
150
	if (cfg_get_metadata_program() &&
	    cfg_get_metadata_format_str()) {
151
152
		char	 buf[BUFSIZ];
		char	*unquoted;
Moritz Grimm's avatar
Moritz Grimm committed
153

154
		mdata_strformat(md, buf, sizeof(buf),
Moritz Grimm's avatar
Moritz Grimm committed
155
		    cfg_get_metadata_format_str());
156
		unquoted = util_utf82char(buf);
157
		custom_songinfo = util_shellquote(unquoted, 0);
Moritz Grimm's avatar
Moritz Grimm committed
158
159
160
161
162
163
		xfree(unquoted);
	} else {
		if (!cfg_get_metadata_program() &&
		    strstr(cfg_decoder_get_program(decoder),
			PLACEHOLDER_TITLE) != NULL) {
			custom_songinfo = xstrdup("");
164
		} else {
Moritz Grimm's avatar
Moritz Grimm committed
165
			custom_songinfo = xstrdup(songinfo);
166
167
		}
	}
Moritz Grimm's avatar
Moritz Grimm committed
168
	xfree(songinfo);
169

Moritz Grimm's avatar
Moritz Grimm committed
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
	memset(dicts, 0, sizeof(dicts));
	dicts[0].from = PLACEHOLDER_ARTIST;
	dicts[0].to = artist;
	dicts[1].from = PLACEHOLDER_ALBUM;
	dicts[1].to = album;
	dicts[2].from = PLACEHOLDER_TITLE;
	dicts[2].to = title;
	dicts[3].from = PLACEHOLDER_TRACK;
	dicts[3].to = filename_quoted;
	dicts[4].from = PLACEHOLDER_METADATA;
	dicts[4].to = custom_songinfo;

	if (!cfg_get_metadata_program() &&
	    strstr(cfg_encoder_get_program(encoder),
		PLACEHOLDER_TITLE) != NULL) {
		xfree(custom_songinfo);
		dicts[4].to = custom_songinfo = xstrdup("");
187
	}
188

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
	dec_str = util_expand_words(cfg_decoder_get_program(decoder), dicts);
	cmd_str_size = strlen(dec_str) + 1;
	if (cfg_encoder_get_program(encoder)) {
		enc_str = util_expand_words(cfg_encoder_get_program(encoder),
		    dicts);
		cmd_str_size += strlen(" | ") + strlen(enc_str);
		cmd_str = xcalloc(cmd_str_size, sizeof(char));
		snprintf(cmd_str, cmd_str_size, "%s | %s", dec_str, enc_str);
		xfree(enc_str);
		xfree(dec_str);
	} else {
		cmd_str = xcalloc(cmd_str_size, sizeof(char));
		snprintf(cmd_str, cmd_str_size, "%s", dec_str);
		xfree(dec_str);
	}
204

Moritz Grimm's avatar
Moritz Grimm committed
205
206
207
208
209
210
211
	xfree(artist);
	xfree(album);
	xfree(title);
	xfree(filename_quoted);
	xfree(custom_songinfo);

	return (cmd_str);
212
}
oddsock's avatar
oddsock committed
213

214
static FILE *
215
216
openResource(stream_t stream, const char *filename, int *popenFlag,
	     mdata_t *md_p, int *isStdin, long *songLen)
217
{
218
219
220
221
	FILE		*filep = NULL;
	char		 extension[25];
	char		*p = NULL;
	char		*pCommandString = NULL;
222
	mdata_t 	 md;
223
	cfg_stream_t	 cfg_stream = stream_get_cfg_stream(stream);
oddsock's avatar
oddsock committed
224

225
226
	if (md_p != NULL)
		*md_p = NULL;
227
228
	if (songLen != NULL)
		*songLen = 0;
229

230
	if ((isStdin && *isStdin) ||
231
	    strcasecmp(filename, "stdin") == 0) {
232
		if (cfg_get_metadata_program()) {
233
234
235
236
			md = mdata_create();

			if (0 > mdata_run_program(md, cfg_get_metadata_program()) ||
			    0 > stream_set_metadata(stream, md, NULL)) {
237
				mdata_destroy(&md);
238
239
				return (NULL);
			}
240

241
242
			if (md_p != NULL)
				*md_p = md;
243
			else
244
				mdata_destroy(&md);
245
		}
246

247
248
		if (isStdin != NULL)
			*isStdin = 1;
249
		filep = stdin;
250
		return (filep);
oddsock's avatar
oddsock committed
251
	}
252

253
254
255
256
	if (isStdin != NULL)
		*isStdin = 0;

	extension[0] = '\0';
257
	p = strrchr(filename, '.');
258
259
260
	if (p != NULL)
		strlcpy(extension, p, sizeof(extension));
	for (p = extension; *p != '\0'; p++)
261
		*p = (char)tolower((int)*p);
262
263

	if (strlen(extension) == 0) {
264
		log_error("%s: cannot determine file type", filename);
265
266
267
		return (filep);
	}

268
	md = mdata_create();
269
	if (cfg_get_metadata_program()) {
270
271
		if (0 > mdata_run_program(md, cfg_get_metadata_program()))
			mdata_destroy(&md);
272
	} else {
273
274
		if (0 > mdata_parse_file(md, filename))
			mdata_destroy(&md);
275
	}
276
277
	if (NULL == md)
		return (NULL);
278
	if (songLen != NULL)
279
		*songLen = mdata_get_length(md);
280
281

	*popenFlag = 0;
282
	if (cfg_stream_get_encoder(cfg_stream)) {
283
		int	stderr_fd = -1;
284

285
		pCommandString = _build_reencode_cmd(extension, filename,
286
		    cfg_stream, md);
287
288
		if (md_p != NULL)
			*md_p = md;
289
		else
290
			mdata_destroy(&md);
291
		log_info("running command: %s", pCommandString);
292

293
		if (cfg_get_program_quiet_stderr()) {
294
295
			int fd;

296
			stderr_fd = dup(fileno(stderr));
297
298
299
300
			if (0 > stderr_fd) {
				log_alert("dup: %s", strerror(errno));
				exit(1);
			}
301
			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
302
303
				log_alert("%s: %s", _PATH_DEVNULL,
				    strerror(errno));
304
				exit(1);
305
306
			}

307
			dup2(fd, fileno(stderr));
308
			close(fd);
309
310
311
312
313
314
315
		}

		fflush(NULL);
		errno = 0;
		if ((filep = popen(pCommandString, "r")) == NULL) {
			/* popen() does not set errno reliably ... */
			if (errno)
316
317
				log_error("execution error: %s: %s",
				    pCommandString, strerror(errno));
318
			else
319
320
				log_error("execution error: %s",
				    pCommandString);
321
322
323
324
		} else {
			*popenFlag = 1;
		}
		xfree(pCommandString);
325

326
		if (cfg_get_program_quiet_stderr())
327
			dup2(stderr_fd, fileno(stderr));
328

329
		if (stderr_fd != -1)
330
			close(stderr_fd);
331

332
		return (filep);
333
	}
334

335
336
	if (md_p != NULL)
		*md_p = md;
337
	else
338
		mdata_destroy(&md);
339

340
341
	if ((filep = fopen(filename, "rb")) == NULL) {
		log_error("%s: %s", filename, strerror(errno));
342
343
		return (NULL);
	}
344
345

	return (filep);
346
347
}

348
int
349
reconnect(stream_t stream)
350
351
{
	unsigned int	i;
352
	cfg_server_t	cfg_server = stream_get_cfg_server(stream);
353
354
355

	i = 0;
	while (++i) {
356
		if (cfg_server_get_reconnect_attempts(cfg_server) > 0)
357
			log_notice("reconnect: %s: attempt #%u/%u ...",
358
359
			    cfg_server_get_hostname(cfg_server), i,
			    cfg_server_get_reconnect_attempts(cfg_server));
360
		else
361
			log_notice("reconnect: %s: attempt #%u ...",
362
			    cfg_server_get_hostname(cfg_server), i);
363

364
365
		stream_disconnect(stream);
		if (0 == stream_connect(stream)) {
366
			log_notice("reconnect: %s: success",
367
			    cfg_server_get_hostname(cfg_server));
368
			return (0);
369
370
		}

371
372
		if (cfg_server_get_reconnect_attempts(cfg_server) > 0 &&
		    i >= cfg_server_get_reconnect_attempts(cfg_server))
373
374
			break;

375
		if (quit)
376
			return (-1);
377
378
		else
			sleep(5);
379
380
	};

381
	log_warning("reconnect failed: giving up");
382
383

	return (-1);
384
385
}

386
const char *
387
getTimeString(long seconds)
388
{
389
	static char	str[25];
390
	long		secs, mins, hours;
391
392
393
394
395
396
397
398
399
400

	if (seconds < 0)
		return (NULL);

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

401
	snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
402
403
404
	return ((const char *)str);
}

405
int
406
sendStream(stream_t stream, FILE *filepstream, const char *fileName,
407
	   int isStdin, const char *songLenStr, struct timespec *tv)
408
{
409
	char		  buff[4096];
410
411
412
413
414
	size_t		  bytes_read, total, oldTotal;
	int		  ret;
	double		  kbps = -1.0;
	struct timespec	  timeStamp, *startTime = tv;
	struct timespec	  callTime, currentTime;
415
	cfg_server_t	  cfg_server = stream_get_cfg_server(stream);
416
	cfg_intake_t	  cfg_intake = stream_get_cfg_intake(stream);
417

418
	clock_gettime(CLOCK_MONOTONIC, &callTime);
419

420
	timeStamp.tv_sec = startTime->tv_sec;
421
	timeStamp.tv_nsec = startTime->tv_nsec;
422
423

	total = oldTotal = 0;
424
	ret = STREAM_DONE;
425
426
427
	while ((bytes_read = fread(buff, 1, sizeof(buff), filepstream)) > 0) {
		if (!stream_get_connected(stream)) {
			log_warning("%s: connection lost",
428
			    cfg_server_get_hostname(cfg_server));
429
430
431
432
			if (0 > reconnect(stream)) {
				ret = STREAM_SERVERR;
				break;
			}
433
434
		}

435
		stream_sync(stream);
436

437
438
		if (0 > stream_send(stream, buff, bytes_read)) {
			if (0 > reconnect(stream))
439
				ret = STREAM_SERVERR;
440
			break;
441
442
		}

443
444
		if (quit)
			break;
445
446
		if (rereadPlaylist_notify) {
			rereadPlaylist_notify = 0;
447
			if (CFG_INTAKE_PLAYLIST == cfg_intake_get_type(cfg_intake))
448
				log_notice("HUP signal received: playlist re-read scheduled");
449
450
451
		}
		if (skipTrack) {
			skipTrack = 0;
452
			ret = STREAM_SKIP;
453
454
			break;
		}
455

456
		clock_gettime(CLOCK_MONOTONIC, &currentTime);
457
458

		if (queryMetadata ||
459
		    (0 <= cfg_get_metadata_refresh_interval() &&
460
461
			(currentTime.tv_sec - callTime.tv_sec >=
			    cfg_get_metadata_refresh_interval()))) {
462
			queryMetadata = 0;
463
			if (cfg_get_metadata_program()) {
464
				ret = STREAM_UPDMDATA;
465
				break;
oddsock's avatar
oddsock committed
466
			}
467
468
		}

469
		total += bytes_read;
470
		if (cfg_get_program_rtstatus_output()) {
471
			double	oldTime, newTime;
472

473
			if (!isStdin && playlistMode) {
474
475
				if (CFG_INTAKE_PROGRAM == cfg_intake_get_type(cfg_intake)) {
					char *tmp = xstrdup(cfg_intake_get_filename(cfg_intake));
476
					printf("  [%s]", basename(tmp));
477
478
479
					xfree(tmp);
				} else
					printf("  [%4lu/%-4lu]",
480
481
					    playlist_get_position(playlist),
					    playlist_get_num_items(playlist));
482
			}
483
484

			oldTime = (double)timeStamp.tv_sec
485
			    + (double)timeStamp.tv_nsec / 1000000000.0;
486
			newTime = (double)currentTime.tv_sec
487
			    + (double)currentTime.tv_nsec / 1000000000.0;
488
489
			if (songLenStr == NULL)
				printf("  [ %s]",
490
491
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec));
492
493
			else
				printf("  [ %s/%s]",
494
495
496
				    getTimeString(currentTime.tv_sec -
					startTime->tv_sec),
				    songLenStr);
497
			if (newTime - oldTime >= 1.0) {
498
				kbps = (((double)(total - oldTotal)
499
				    / (newTime - oldTime)) * 8.0) / 1000.0;
500
				timeStamp.tv_sec = currentTime.tv_sec;
501
				timeStamp.tv_nsec = currentTime.tv_nsec;
502
503
504
505
506
507
508
509
510
511
				oldTotal = total;
			}
			if (kbps < 0)
				printf("                 ");
			else
				printf("  [%8.2f kbps]", kbps);

			printf("  \r");
			fflush(stdout);
		}
oddsock's avatar
oddsock committed
512
	}
513
514
515
	if (ferror(filepstream)) {
		if (errno == EINTR) {
			clearerr(filepstream);
516
			ret = STREAM_CONT;
517
		} else if (errno == EBADF && isStdin)
518
			log_notice("no (more) data available on standard input");
519
		else
520
521
			log_error("sendStream: %s: %s", fileName,
			    strerror(errno));
522
523
	}

524
	return (ret);
525
526
527
}

int
528
streamFile(stream_t stream, const char *fileName)
529
530
531
{
	FILE		*filepstream = NULL;
	int		 popenFlag = 0;
532
	char		*songLenStr = NULL;
533
534
	int		 ret, retval = 0;
	long		 songLen;
535
	mdata_t 	 md = NULL;
536
	struct timespec	 startTime;
537
	cfg_stream_t	 cfg_stream = stream_get_cfg_stream(stream);
538
539
	cfg_intake_t	 cfg_intake = stream_get_cfg_intake(stream);
	int		 isStdin = cfg_intake_get_type(cfg_intake) == CFG_INTAKE_STDIN;
540

541
	if ((filepstream = openResource(stream, fileName, &popenFlag, &md, &isStdin, &songLen))
542
	    == NULL) {
543
		mdata_destroy(&md);
544
		if (++resource_errors > 100) {
545
			log_error("too many errors; giving up");
546
547
			return (0);
		}
548
549
		/* Continue with next resource on failure: */
		return (1);
550
551
	}
	resource_errors = 0;
552

553
554
555
	if (md != NULL) {
		const char	*tmp;
		char		*metaData;
556

557
558
559
		tmp = mdata_get_songinfo(md) ?
		    mdata_get_songinfo(md) : mdata_get_name(md);
		metaData = util_utf82char(tmp);
560
561
		log_notice("streaming: %s (%s)", metaData,
		    isStdin ? "stdin" : fileName);
562
		xfree(metaData);
563
564

		/* MP3 streams are special, so set the metadata explicitly: */
565
		if (CFG_STREAM_MP3 == cfg_stream_get_format(cfg_stream))
566
			stream_set_metadata(stream, md, NULL);
567

568
		mdata_destroy(&md);
569
	} else if (isStdin)
570
		log_notice("streaming: standard input");
571

moritz's avatar
moritz committed
572
	if (songLen > 0)
573
		songLenStr = xstrdup(getTimeString(songLen));
574
	clock_gettime(CLOCK_MONOTONIC, &startTime);
575
	do {
576
		ret = sendStream(stream, filepstream, fileName, isStdin,
577
		    songLenStr, &startTime);
578
579
		if (quit)
			break;
580
		if (ret != STREAM_DONE) {
581
582
			if ((skipTrack && rereadPlaylist) ||
			    (skipTrack && queryMetadata)) {
583
				skipTrack = 0;
584
585
586
587
588
				ret = STREAM_CONT;
			}
			if (queryMetadata && rereadPlaylist) {
				queryMetadata = 0;
				ret = STREAM_CONT;
589
			}
590
			if (ret == STREAM_SKIP || skipTrack) {
591
				skipTrack = 0;
592
593
				if (!isStdin)
					log_notice("USR1 signal received: skipping current track");
594
				retval = 1;
595
596
				ret = STREAM_DONE;
			}
597
598
			if (ret == STREAM_UPDMDATA || queryMetadata) {
				queryMetadata = 0;
599
				if (cfg_get_metadata_no_updates())
600
					continue;
601
				if (cfg_get_metadata_program()) {
602
					char		*mdataStr = NULL;
603
					mdata_t 	 prog_md;
604

605
					log_info("running metadata program: %s",
606
					    cfg_get_metadata_program());
607
608
609
610
					prog_md = mdata_create();
					if (0 > mdata_run_program(md, cfg_get_metadata_program()) ||
					    0 > stream_set_metadata(stream, prog_md, &mdataStr)) {
						mdata_destroy(&prog_md);
611
612
						retval = 0;
						ret = STREAM_DONE;
613
						continue;
614
					}
615
					mdata_destroy(&prog_md);
616
					log_info("new metadata: %s", mdataStr);
617
618
619
					xfree(mdataStr);
				}
			}
620
621
622
			if (ret == STREAM_SERVERR) {
				retval = 0;
				ret = STREAM_DONE;
623
624
625
			}
		} else
			retval = 1;
626
	} while (ret != STREAM_DONE);
627
628

	if (popenFlag)
oddsock's avatar
oddsock committed
629
		pclose(filepstream);
Moritz Grimm's avatar
Moritz Grimm committed
630
	else if (!isStdin)
oddsock's avatar
oddsock committed
631
		fclose(filepstream);
632

633
634
635
	if (songLenStr != NULL)
		xfree(songLenStr);

636
	return (retval);
oddsock's avatar
oddsock committed
637
}
638

639
int
640
streamPlaylist(stream_t stream)
641
642
{
	const char	*song;
moritz's avatar
moritz committed
643
	char		 lastSong[PATH_MAX];
644
	cfg_intake_t	 cfg_intake = stream_get_cfg_intake(stream);
645
646

	if (playlist == NULL) {
647
648
649
		switch (cfg_intake_get_type(cfg_intake)) {
		case CFG_INTAKE_PROGRAM:
			if ((playlist = playlist_program(cfg_intake_get_filename(cfg_intake))) == NULL)
650
				return (0);
651
			break;
652
		case CFG_INTAKE_STDIN:
653
654
655
656
			if ((playlist = playlist_read(NULL)) == NULL)
				return (0);
			break;
		default:
657
			if ((playlist = playlist_read(cfg_intake_get_filename(cfg_intake))) == NULL)
658
				return (0);
659
			if (playlist_get_num_items(playlist) == 0)
660
				log_warning("%s: playlist empty",
661
				    cfg_intake_get_filename(cfg_intake));
662
			break;
663
		}
664
	} else {
665
666
667
668
669
		/*
		 * XXX: This preserves traditional behavior, however,
		 *      rereading the playlist after each walkthrough seems a
		 *      bit more logical.
		 */
670
		playlist_rewind(playlist);
671
	}
672

673
674
	if (CFG_INTAKE_PROGRAM != cfg_intake_get_type(cfg_intake) &&
	    cfg_intake_get_shuffle(cfg_intake))
675
676
677
678
		playlist_shuffle(playlist);

	while ((song = playlist_get_next(playlist)) != NULL) {
		strlcpy(lastSong, song, sizeof(lastSong));
679
		if (!streamFile(stream, song))
680
			return (0);
681
682
		if (quit)
			break;
683
684
		if (rereadPlaylist) {
			rereadPlaylist = rereadPlaylist_notify = 0;
685
			if (CFG_INTAKE_PROGRAM == cfg_intake_get_type(cfg_intake))
686
				continue;
687
			log_notice("rereading playlist");
688
689
			if (!playlist_reread(&playlist))
				return (0);
690
			if (cfg_intake_get_shuffle(cfg_intake))
691
692
693
694
				playlist_shuffle(playlist);
			else {
				playlist_goto_entry(playlist, lastSong);
				playlist_skip_next(playlist);
oddsock's avatar
oddsock committed
695
			}
696
			continue;
697
		}
oddsock's avatar
oddsock committed
698
	}
699

700
	return (1);
oddsock's avatar
oddsock committed
701
702
}

703
int
704
ez_shutdown(int exitval)
705
{
706
707
708
	if (main_stream)
		stream_destroy(&main_stream);

709
	stream_exit();
710
	playlist_exit();
Moritz Grimm's avatar
Moritz Grimm committed
711
	log_exit();
712
	cfg_exit();
713
714
715
716

	return (exitval);
}

717
int
718
main(int argc, char *argv[])
oddsock's avatar
oddsock committed
719
{
720
	int		 ret, cont;
721
	const char	*errstr;
722
723
	extern char	*optarg;
	extern int	 optind;
724
	struct sigaction act;
moritz's avatar
moritz committed
725
	unsigned int	 i;
726
727
	cfg_server_t	 cfg_server;
	cfg_stream_t	 cfg_stream;
728
	cfg_intake_t	 cfg_intake;
729

Moritz Grimm's avatar
Moritz Grimm committed
730
	ret = 1;
731
732
	if (0 > cfg_init() ||
	    0 > cmdline_parse(argc, argv, &ret) ||
Moritz Grimm's avatar
Moritz Grimm committed
733
	    0 > log_init(cfg_get_program_name()) ||
734
	    0 > playlist_init() ||
735
	    0 > cfg_file_reload() ||
736
	    0 > stream_init())
Moritz Grimm's avatar
Moritz Grimm committed
737
		return (ez_shutdown(ret));
738

Moritz Grimm's avatar
Moritz Grimm committed
739
740
	log_set_verbosity(cfg_get_program_verbosity());

741
742
	if (0 > cfg_check(&errstr)) {
		log_error("%s: %s", cfg_get_program_config_file(), errstr);
743
		return (ez_shutdown(2));
oddsock's avatar
oddsock committed
744
	}
745

746
747
	main_stream = stream_create(CFG_DEFAULT);
	if (0 > stream_configure(main_stream))
748
		return (ez_shutdown(1));
749
750
	cfg_server = stream_get_cfg_server(main_stream);
	cfg_stream = stream_get_cfg_stream(main_stream);
751
	cfg_intake = stream_get_cfg_intake(main_stream);
oddsock's avatar
oddsock committed
752

753
754
	memset(&act, 0, sizeof(act));
	act.sa_handler = sig_handler;
755
#ifdef SA_RESTART
756
	act.sa_flags = SA_RESTART;
757
#endif
moritz's avatar
moritz committed
758
759
	for (i = 0; i < sizeof(ezstream_signals) / sizeof(int); i++) {
		if (sigaction(ezstream_signals[i], &act, NULL) == -1) {
760
			log_syserr(ERROR, errno, "sigaction");
761
			return (ez_shutdown(1));
moritz's avatar
moritz committed
762
763
		}
	}
764
765
766
767
	/*
	 * Ignore SIGPIPE, which has been seen to give a long-running ezstream
	 * process trouble. EOF and/or EPIPE are also easier to handle.
	 */
768
769
770
#ifndef SIG_IGN
# define SIG_IGN	 (void (*)(int))1
#endif /* !SIG_IGN */
771
772
	act.sa_handler = SIG_IGN;
	if (sigaction(SIGPIPE, &act, NULL) == -1) {
773
		log_syserr(ERROR, errno, "sigaction");
774
775
		return (ez_shutdown(1));
	}
776

777
	if (0 > util_write_pid_file(cfg_get_program_pid_file()))
778
779
		log_syserr(WARNING, errno, cfg_get_program_pid_file());

780
	if (0 > stream_connect(main_stream)) {
781
782
783
784
		log_error("initial server connection failed");
		return (ez_shutdown(1));
	}
	log_notice("connected: %s://%s:%u%s",
785
786
787
788
	    cfg_server_get_protocol_str(cfg_server),
	    cfg_server_get_hostname(cfg_server),
	    cfg_server_get_port(cfg_server),
	    cfg_stream_get_mountpoint(cfg_stream));
789

790
791
792
793
794
	if (CFG_INTAKE_PROGRAM == cfg_intake_get_type(cfg_intake) ||
	    CFG_INTAKE_PLAYLIST == cfg_intake_get_type(<