ezstream.c 18.8 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);
Moritz Grimm's avatar
Moritz Grimm committed
56
char *		_build_reencode_cmd(const char *, const char *, metadata_t);
57
58
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 *
Moritz Grimm's avatar
Moritz Grimm committed
92
_build_reencode_cmd(const char *extension, const char *fileName,
93
    metadata_t mdata)
94
{
Moritz Grimm's avatar
Moritz Grimm committed
95
96
97
98
99
100
101
102
103
104
	cfg_decoder_t		 decoder;
	cfg_encoder_t		 encoder;
	char			*artist, *album, *title, *songinfo, *tmp;
	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;
105
106
107
108

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

Moritz Grimm's avatar
Moritz Grimm committed
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
	tmp = util_utf82char(metadata_get_artist(mdata));
	artist = util_shellquote(tmp);
	xfree(tmp);

	tmp = util_utf82char(metadata_get_album(mdata));
	album = util_shellquote(tmp);
	xfree(tmp);

	tmp = util_utf82char(metadata_get_title(mdata));
	title = util_shellquote(tmp);
	xfree(tmp);

	tmp = util_utf82char(metadata_get_string(mdata));
	songinfo = util_shellquote(tmp);
	xfree(tmp);

	filename_quoted = util_shellquote(fileName);

137
	/*
Moritz Grimm's avatar
Moritz Grimm committed
138
139
140
141
142
	 * if (prog && format)
	 *    metatoformat
	 * else
	 *   if (!prog && title)
	 *     emptymeta
143
	 *   else
Moritz Grimm's avatar
Moritz Grimm committed
144
	 *     replacemeta
145
	 */
Moritz Grimm's avatar
Moritz Grimm committed
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
	if (cfg_get_metadata_program() &&
	    cfg_get_metadata_format_str()) {
		char	*utf8, *unquoted;

		utf8 = metadata_format_string(mdata,
		    cfg_get_metadata_format_str());
		unquoted = util_utf82char(utf8);
		xfree(utf8);
		custom_songinfo = util_shellquote(unquoted);
		xfree(unquoted);
	} else {
		if (!cfg_get_metadata_program() &&
		    strstr(cfg_decoder_get_program(decoder),
			PLACEHOLDER_TITLE) != NULL) {
			custom_songinfo = xstrdup("");
161
		} else {
Moritz Grimm's avatar
Moritz Grimm committed
162
			custom_songinfo = xstrdup(songinfo);
163
164
		}
	}
Moritz Grimm's avatar
Moritz Grimm committed
165
	xfree(songinfo);
166

Moritz Grimm's avatar
Moritz Grimm committed
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
	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;

	dec_str = util_expand_words(cfg_decoder_get_program(decoder), dicts);

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

Moritz Grimm's avatar
Moritz Grimm committed
188
	enc_str = util_expand_words(cfg_encoder_get_program(encoder), dicts);
189

Moritz Grimm's avatar
Moritz Grimm committed
190
191
192
	cmd_str_size = strlen(dec_str) + strlen(" | ") + strlen(enc_str) + 1;
	cmd_str = xcalloc(cmd_str_size, sizeof(char));
	snprintf(cmd_str, cmd_str_size, "%s | %s", dec_str, enc_str);
193
194
	xfree(dec_str);
	xfree(enc_str);
195

Moritz Grimm's avatar
Moritz Grimm committed
196
197
198
199
200
201
202
	xfree(artist);
	xfree(album);
	xfree(title);
	xfree(filename_quoted);
	xfree(custom_songinfo);

	return (cmd_str);
203
}
oddsock's avatar
oddsock committed
204

205
metadata_t
206
getMetadata(const char *fileName)
moritz's avatar
moritz committed
207
{
208
	metadata_t	mdata;
209

210
211
212
	if (cfg_get_metadata_program()) {
		if (NULL == (mdata = metadata_program(fileName,
		    cfg_get_metadata_normalize_strings())))
213
214
			return (NULL);

215
		if (!metadata_program_update(mdata, METADATA_ALL)) {
216
			metadata_free(&mdata);
217
			return (NULL);
218
219
		}
	} else {
220
221
		if (NULL == (mdata = metadata_file(fileName,
		    cfg_get_metadata_normalize_strings())))
222
			return (NULL);
moritz's avatar
moritz committed
223

224
225
		if (!metadata_file_update(mdata)) {
			metadata_free(&mdata);
226
			return (NULL);
227
		}
228
	}
229

230
231
232
	return (mdata);
}

233
FILE *
234
235
openResource(stream_t stream, const char *fileName, int *popenFlag,
	     metadata_t *mdata_p, int *isStdin, long *songLen)
236
{
237
238
239
240
	FILE		*filep = NULL;
	char		 extension[25];
	char		*p = NULL;
	char		*pCommandString = NULL;
241
	metadata_t	 mdata;
oddsock's avatar
oddsock committed
242

243
244
	if (mdata_p != NULL)
		*mdata_p = NULL;
245
246
	if (songLen != NULL)
		*songLen = 0;
247

248
249
	if ((isStdin && *isStdin) ||
	    strcasecmp(fileName, "stdin") == 0) {
250
251
		if (cfg_get_metadata_program()) {
			if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
252
				return (NULL);
253
			if (0 > stream_set_metadata(stream, mdata, NULL)) {
254
255
256
				metadata_free(&mdata);
				return (NULL);
			}
257
258
259
260
			if (mdata_p != NULL)
				*mdata_p = mdata;
			else
				metadata_free(&mdata);
261
		}
262

263
264
		if (isStdin != NULL)
			*isStdin = 1;
265
		filep = stdin;
266
		return (filep);
oddsock's avatar
oddsock committed
267
	}
268

269
270
271
272
	if (isStdin != NULL)
		*isStdin = 0;

	extension[0] = '\0';
273
274
275
276
	p = strrchr(fileName, '.');
	if (p != NULL)
		strlcpy(extension, p, sizeof(extension));
	for (p = extension; *p != '\0'; p++)
277
		*p = (char)tolower((int)*p);
278
279

	if (strlen(extension) == 0) {
280
		log_error("%s: cannot determine file type", fileName);
281
282
283
		return (filep);
	}

284
285
	if (cfg_get_metadata_program()) {
		if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
286
287
288
289
290
			return (NULL);
	} else {
		if ((mdata = getMetadata(fileName)) == NULL)
			return (NULL);
	}
291
292
	if (songLen != NULL)
		*songLen = metadata_get_length(mdata);
293
294

	*popenFlag = 0;
295
	if (cfg_get_stream_encoder()) {
296
		int	stderr_fd = -1;
297

Moritz Grimm's avatar
Moritz Grimm committed
298
		pCommandString = _build_reencode_cmd(extension, fileName,
299
		    mdata);
300
301
302
303
		if (mdata_p != NULL)
			*mdata_p = mdata;
		else
			metadata_free(&mdata);
304
		log_info("running command: %s", pCommandString);
305

306
		if (cfg_get_program_quiet_stderr()) {
307
308
			int fd;

309
			stderr_fd = dup(fileno(stderr));
310
			if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) {
311
312
				log_alert("%s: %s", _PATH_DEVNULL,
				    strerror(errno));
313
				exit(1);
314
315
			}

316
317
318
319
320
321
322
323
324
325
			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)
326
327
				log_error("execution error: %s: %s",
				    pCommandString, strerror(errno));
328
			else
329
330
				log_error("execution error: %s",
				    pCommandString);
331
332
333
334
		} else {
			*popenFlag = 1;
		}
		xfree(pCommandString);
335

336
		if (cfg_get_program_quiet_stderr())
337
			dup2(stderr_fd, fileno(stderr));
338
339
340

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

342
		return (filep);
343
	}
344

345
346
347
348
	if (mdata_p != NULL)
		*mdata_p = mdata;
	else
		metadata_free(&mdata);
349

350
351
352
353
	if ((filep = fopen(fileName, "rb")) == NULL) {
		log_error("%s: %s", fileName, strerror(errno));
		return (NULL);
	}
354
355

	return (filep);
356
357
}

358
int
359
reconnect(stream_t stream)
360
361
362
363
364
{
	unsigned int	i;

	i = 0;
	while (++i) {
365
		if (cfg_get_server_reconnect_attempts() > 0)
366
			log_notice("reconnect: %s: attempt #%u/%u ...",
367
368
			    cfg_get_server_hostname(), i,
			    cfg_get_server_reconnect_attempts());
369
		else
370
			log_notice("reconnect: %s: attempt #%u ...",
371
			    cfg_get_server_hostname(), i);
372

373
374
		stream_disconnect(stream);
		if (0 == stream_connect(stream)) {
375
			log_notice("reconnect: %s: success",
376
			    cfg_get_server_hostname());
377
			return (0);
378
379
		}

380
381
		if (cfg_get_server_reconnect_attempts() > 0 &&
		    i >= cfg_get_server_reconnect_attempts())
382
383
			break;

384
		if (quit)
385
			return (-1);
386
387
		else
			sleep(5);
388
389
	};

390
	log_warning("reconnect failed: giving up");
391
392

	return (-1);
393
394
}

395
const char *
396
getTimeString(long seconds)
397
398
{
	static char	str[20];
399
	long		secs, mins, hours;
400
401
402
403
404
405
406
407
408
409

	if (seconds < 0)
		return (NULL);

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

410
	snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs);
411
412
413
	return ((const char *)str);
}

414
int
415
sendStream(stream_t stream, FILE *filepstream, const char *fileName,
416
	   int isStdin, const char *songLenStr, struct timespec *tv)
417
{
418
	char		  buff[4096];
419
420
421
422
423
	size_t		  bytes_read, total, oldTotal;
	int		  ret;
	double		  kbps = -1.0;
	struct timespec	  timeStamp, *startTime = tv;
	struct timespec	  callTime, currentTime;
424

425
	clock_gettime(CLOCK_MONOTONIC, &callTime);
426

427
	timeStamp.tv_sec = startTime->tv_sec;
428
	timeStamp.tv_nsec = startTime->tv_nsec;
429
430

	total = oldTotal = 0;
431
	ret = STREAM_DONE;
432
433
434
435
436
437
438
439
	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;
			}
440
441
		}

442
		stream_sync(stream);
443

444
445
		if (0 > stream_send(stream, buff, bytes_read)) {
			if (0 > reconnect(stream))
446
				ret = STREAM_SERVERR;
447
			break;
448
449
		}

450
451
		if (quit)
			break;
452
453
		if (rereadPlaylist_notify) {
			rereadPlaylist_notify = 0;
454
			if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
455
				log_notice("HUP signal received: playlist re-read scheduled");
456
457
458
		}
		if (skipTrack) {
			skipTrack = 0;
459
			ret = STREAM_SKIP;
460
461
			break;
		}
462

463
		clock_gettime(CLOCK_MONOTONIC, &currentTime);
464
465

		if (queryMetadata ||
466
		    (0 <= cfg_get_metadata_refresh_interval() &&
467
468
			(currentTime.tv_sec - callTime.tv_sec >=
			    cfg_get_metadata_refresh_interval()))) {
469
			queryMetadata = 0;
470
			if (cfg_get_metadata_program()) {
471
				ret = STREAM_UPDMDATA;
472
				break;
oddsock's avatar
oddsock committed
473
			}
474
475
		}

476
		total += bytes_read;
477
		if (cfg_get_program_rtstatus_output()) {
478
			double	oldTime, newTime;
479

480
			if (!isStdin && playlistMode) {
481
482
				if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
					char *tmp = xstrdup(cfg_get_media_filename());
483
					printf("  [%s]", basename(tmp));
484
485
486
					xfree(tmp);
				} else
					printf("  [%4lu/%-4lu]",
487
488
					    playlist_get_position(playlist),
					    playlist_get_num_items(playlist));
489
			}
490
491

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

			printf("  \r");
			fflush(stdout);
		}
oddsock's avatar
oddsock committed
519
	}
520
521
522
	if (ferror(filepstream)) {
		if (errno == EINTR) {
			clearerr(filepstream);
523
			ret = STREAM_CONT;
524
		} else if (errno == EBADF && isStdin)
525
			log_notice("no (more) data available on standard input");
526
		else
527
528
			log_error("sendStream: %s: %s", fileName,
			    strerror(errno));
529
530
	}

531
	return (ret);
532
533
534
}

int
535
streamFile(stream_t stream, const char *fileName)
536
537
538
{
	FILE		*filepstream = NULL;
	int		 popenFlag = 0;
539
	char		*songLenStr = NULL;
540
	int		 isStdin = cfg_get_media_type() == CFG_MEDIA_STDIN;
541
542
	int		 ret, retval = 0;
	long		 songLen;
543
	metadata_t	 mdata;
544
	struct timespec	 startTime;
545

546
	if ((filepstream = openResource(stream, fileName, &popenFlag, &mdata, &isStdin, &songLen))
547
548
	    == NULL) {
		if (++resource_errors > 100) {
549
			log_error("too many errors; giving up");
550
551
			return (0);
		}
552
553
		/* Continue with next resource on failure: */
		return (1);
554
555
	}
	resource_errors = 0;
556

557
	if (mdata != NULL) {
558
		char	*tmp, *metaData;
559

560
		tmp = metadata_assemble_string(mdata);
Moritz Grimm's avatar
Moritz Grimm committed
561
		if ((metaData = util_utf82char(tmp)) == NULL)
562
			metaData = xstrdup("(unknown title)");
563
		xfree(tmp);
564
565
		log_notice("streaming: %s (%s)", metaData,
		    isStdin ? "stdin" : fileName);
566
		xfree(metaData);
567
568

		/* MP3 streams are special, so set the metadata explicitly: */
569
		if (CFG_STREAM_MP3 == cfg_get_stream_format())
570
			stream_set_metadata(stream, mdata, NULL);
571
572

		metadata_free(&mdata);
573
	} else if (isStdin)
574
		log_notice("streaming: standard input");
575

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

609
					log_info("running metadata program: %s",
610
611
					    cfg_get_metadata_program());
					if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
612
613
614
615
						retval = 0;
						ret = STREAM_DONE;
						continue;
					}
Moritz Grimm's avatar
Moritz Grimm committed
616
					if (0 > stream_set_metadata(stream, prog_mdata, &mdataStr)) {
617
618
						retval = 0;
						ret = STREAM_DONE;
Moritz Grimm's avatar
Moritz Grimm committed
619
						metadata_free(&prog_mdata);
620
						continue;
621
					}
622
					metadata_free(&prog_mdata);
623
					log_info("new metadata: %s", mdataStr);
624
625
626
					xfree(mdataStr);
				}
			}
627
628
629
			if (ret == STREAM_SERVERR) {
				retval = 0;
				ret = STREAM_DONE;
630
631
632
			}
		} else
			retval = 1;
633
	} while (ret != STREAM_DONE);
634
635

	if (popenFlag)
oddsock's avatar
oddsock committed
636
		pclose(filepstream);
Moritz Grimm's avatar
Moritz Grimm committed
637
	else if (!isStdin)
oddsock's avatar
oddsock committed
638
		fclose(filepstream);
639

640
641
642
	if (songLenStr != NULL)
		xfree(songLenStr);

643
	return (retval);
oddsock's avatar
oddsock committed
644
}
645

646
int
647
streamPlaylist(stream_t stream)
648
649
{
	const char	*song;
moritz's avatar
moritz committed
650
	char		 lastSong[PATH_MAX];
651
652

	if (playlist == NULL) {
653
654
		switch (cfg_get_media_type()) {
		case CFG_MEDIA_PROGRAM:
655
			if ((playlist = playlist_program(cfg_get_media_filename())) == NULL)
656
				return (0);
657
658
659
660
661
662
			break;
		case CFG_MEDIA_STDIN:
			if ((playlist = playlist_read(NULL)) == NULL)
				return (0);
			break;
		default:
663
			if ((playlist = playlist_read(cfg_get_media_filename())) == NULL)
664
				return (0);
665
			if (playlist_get_num_items(playlist) == 0)
666
				log_warning("%s: playlist empty",
667
				    cfg_get_media_filename());
668
			break;
669
		}
670
	} else {
671
672
673
674
675
		/*
		 * XXX: This preserves traditional behavior, however,
		 *      rereading the playlist after each walkthrough seems a
		 *      bit more logical.
		 */
676
		playlist_rewind(playlist);
677
	}
678

679
680
	if (CFG_MEDIA_PROGRAM != cfg_get_media_type() &&
	    cfg_get_media_shuffle())
681
682
683
684
		playlist_shuffle(playlist);

	while ((song = playlist_get_next(playlist)) != NULL) {
		strlcpy(lastSong, song, sizeof(lastSong));
685
		if (!streamFile(stream, song))
686
			return (0);
687
688
		if (quit)
			break;
689
690
		if (rereadPlaylist) {
			rereadPlaylist = rereadPlaylist_notify = 0;
691
			if (CFG_MEDIA_PROGRAM == cfg_get_media_type())
692
				continue;
693
			log_notice("rereading playlist");
694
695
			if (!playlist_reread(&playlist))
				return (0);
696
			if (cfg_get_media_shuffle())
697
698
699
700
				playlist_shuffle(playlist);
			else {
				playlist_goto_entry(playlist, lastSong);
				playlist_skip_next(playlist);
oddsock's avatar
oddsock committed
701
			}
702
			continue;
703
		}
oddsock's avatar
oddsock committed
704
	}
705

706
	return (1);
oddsock's avatar
oddsock committed
707
708
}

709
int
710
ez_shutdown(int exitval)
711
{
712
	stream_exit();
713
714
715
	playlist_exit();
	cfg_encoder_exit();
	cfg_decoder_exit();
Moritz Grimm's avatar
Moritz Grimm committed
716
	log_exit();
717
	cfg_exit();
718
719
720
721

	return (exitval);
}

722
int
723
main(int argc, char *argv[])
oddsock's avatar
oddsock committed
724
{
725
	int		 ret, cont;
726
	const char	*errstr;
727
	stream_t	 stream;
728
729
	extern char	*optarg;
	extern int	 optind;
730
	struct sigaction act;
moritz's avatar
moritz committed
731
	unsigned int	 i;
732

Moritz Grimm's avatar
Moritz Grimm committed
733
	ret = 1;
734
735
	if (0 > cfg_init() ||
	    0 > cmdline_parse(argc, argv, &ret) ||
736
737
738
739
	    0 > log_init() ||
	    0 > cfg_decoder_init() ||
	    0 > cfg_encoder_init() ||
	    0 > playlist_init() ||
740
	    0 > cfg_file_reload() ||
741
	    0 > stream_init())
Moritz Grimm's avatar
Moritz Grimm committed
742
		return (ez_shutdown(ret));
743

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

749
750
	stream = stream_get(STREAM_DEFAULT);
	if (0 > stream_setup(stream))
751
		return (ez_shutdown(1));
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
781
782
783
784
785
786
787
788
789
790
	if (0 > stream_connect(stream)) {
		log_error("initial server connection failed");
		return (ez_shutdown(1));
	}
	log_notice("connected: %s://%s:%u%s",
	    cfg_get_server_protocol_str(), cfg_get_server_hostname(),
	    cfg_get_server_port(), cfg_get_stream_mountpoint());

	if (CFG_MEDIA_PROGRAM == cfg_get_media_type() ||
	    CFG_MEDIA_PLAYLIST == cfg_get_media_type() ||
	    (CFG_MEDIA_AUTODETECT == cfg_get_media_type() &&
791
792
		(util_strrcasecmp(cfg_get_media_filename(), ".m3u") == 0 ||
		    util_strrcasecmp(cfg_get_media_filename(), ".txt") == 0)))
793
794
795
		playlistMode = 1;
	else
		playlistMode = 0;
796

797
798
799
800
801
802
803
804
805
806
807
808
	do {
		if (playlistMode) {
			cont = streamPlaylist(stream);
		} else {
			cont = streamFile(stream,
			    cfg_get_media_filename());
		}
		if (quit)
			break;
		if (cfg_get_media_stream_once())
			break;
	} while (cont);
809

810
	stream_disconnect(stream);
811

812
	if (quit) {
813
814
		if (cfg_get_program_quiet_stderr() &&
		    cfg_get_program_verbosity())
815
816
817
			printf("\r");
		log_notice("INT or TERM signal received");
	}
818

819
	log_info("exiting");
820

821
	playlist_free(&playlist);
oddsock's avatar
oddsock committed
822

823
	return (ez_shutdown(0));
oddsock's avatar
oddsock committed
824
}