/* * ezstream - source client for Icecast with external en-/decoder support * Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski * Copyright (C) 2007, 2009, 2015 Moritz Grimm * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "ezstream.h" #ifdef HAVE_SIGNAL_H # include #endif #include #include "compat.h" #include "configfile.h" #include "metadata.h" #include "playlist.h" #include "util.h" #include "xalloc.h" #include "local_basename.h" #define STREAM_DONE 0 #define STREAM_CONT 1 #define STREAM_SKIP 2 #define STREAM_SERVERR 3 #define STREAM_UPDMDATA 4 #ifdef HAVE___PROGNAME extern char *__progname; #else char *__progname; #endif /* HAVE___PROGNAME */ int nFlag; int mFlag; int qFlag; int sFlag; int vFlag; int metadataFromProgram; EZCONFIG *pezConfig = NULL; playlist_t *playlist = NULL; int playlistMode = 0; unsigned int resource_errors = 0; #ifdef HAVE_SIGNALS const int ezstream_signals[] = { SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2 }; volatile sig_atomic_t rereadPlaylist = 0; volatile sig_atomic_t rereadPlaylist_notify = 0; volatile sig_atomic_t skipTrack = 0; volatile sig_atomic_t queryMetadata = 0; volatile sig_atomic_t quit = 0; #else int rereadPlaylist = 0; int rereadPlaylist_notify = 0; int skipTrack = 0; int queryMetadata = 0; int quit = 0; #endif /* HAVE_SIGNALS */ typedef struct tag_ID3Tag { char tag[3]; char trackName[30]; char artistName[30]; char albumName[30]; char year[3]; char comment[30]; char genre; } ID3Tag; int urlParse(const char *, char **, unsigned short *, char **); char * shellQuote(const char *); char * replaceString(const char *, const char *, const char *); char * buildCommandString(const char *, const char *, metadata_t *); char * getMetadataString(const char *, metadata_t *); metadata_t * getMetadata(const char *); int setMetadata(shout_t *, metadata_t *, char **); FILE * openResource(shout_t *, const char *, int *, metadata_t **, int *, long *); int reconnectServer(shout_t *, int); const char * getTimeString(long); int sendStream(shout_t *, FILE *, const char *, int, const char *, struct timeval *); int streamFile(shout_t *, const char *); int streamPlaylist(shout_t *, const char *); char * getProgname(const char *); void usage(void); void usageHelp(void); int ez_shutdown(int); #ifdef HAVE_SIGNALS void sig_handler(int); # ifndef SIG_IGN # define SIG_IGN (void (*)(int))1 # endif /* !SIG_IGN */ void sig_handler(int sig) { switch (sig) { case SIGTERM: case SIGINT: quit = 1; break; case SIGHUP: rereadPlaylist = 1; rereadPlaylist_notify = 1; break; case SIGUSR1: skipTrack = 1; break; case SIGUSR2: queryMetadata = 1; break; default: break; } } #endif /* HAVE_SIGNALS */ int urlParse(const char *url, char **hostname, unsigned short *port, char **mountname) { const char *p1, *p2, *p3; char tmpPort[6] = ""; size_t hostsiz, mountsiz; const char *errstr; if (hostname == NULL || port == NULL || mountname == NULL) { printf("%s: urlParse(): Internal error: Bad arguments\n", __progname); exit(1); } if (strncmp(url, "http://", strlen("http://")) != 0) { printf("%s: Error: Invalid : Not an HTTP address\n", __progname); return (0); } p1 = url + strlen("http://"); p2 = strchr(p1, ':'); if (p2 == NULL) { printf("%s: Error: Invalid : Missing port\n", __progname); return (0); } hostsiz = (p2 - p1) + 1; *hostname = xmalloc(hostsiz); strlcpy(*hostname, p1, hostsiz); p2++; p3 = strchr(p2, '/'); if (p3 == NULL || p3 - p2 >= (int)sizeof(tmpPort)) { printf("%s: Error: Invalid : Missing mountpoint or too long port number\n", __progname); xfree(*hostname); return (0); } strlcpy(tmpPort, p2, (p3 - p2) + 1UL); *port = (unsigned short)strtonum(tmpPort, 1LL, (long long)USHRT_MAX, &errstr); if (errstr) { printf("%s: Error: Invalid : Port '%s' is %s\n", __progname, tmpPort, errstr); xfree(*hostname); return (0); } mountsiz = strlen(p3) + 1; *mountname = xmalloc(mountsiz); strlcpy(*mountname, p3, mountsiz); return (1); } #define SHELLQUOTE_INLEN_MAX 8191UL char * shellQuote(const char *in) { char *out, *out_p; size_t out_len; const char *in_p; out_len = (strlen(in) > SHELLQUOTE_INLEN_MAX) ? SHELLQUOTE_INLEN_MAX : strlen(in); out_len = out_len * 2 + 2; out = xcalloc(out_len + 1, sizeof(char)); out_p = out; in_p = in; *out_p++ = '\''; out_len--; while (*in_p && out_len > 2) { switch (*in_p) { case '\'': case '\\': *out_p++ = '\\'; out_len--; break; default: break; } *out_p++ = *in_p++; out_len--; } *out_p++ = '\''; return (out); } char * replaceString(const char *source, const char *from, const char *to) { char *to_quoted, *dest; size_t dest_size; const char *p1, *p2; to_quoted = shellQuote(to); dest_size = strlen(source) + strlen(to_quoted) + 1; dest = xcalloc(dest_size, sizeof(char)); p1 = source; p2 = strstr(p1, from); if (p2 != NULL) { strncat(dest, p1, (size_t)(p2 - p1)); strlcat(dest, to_quoted, dest_size); p1 = p2 + strlen(from); } strlcat(dest, p1, dest_size); xfree(to_quoted); return (dest); } char * buildCommandString(const char *extension, const char *fileName, metadata_t *mdata) { char *commandString = NULL; size_t commandStringLen = 0; char *encoder = NULL; char *decoder = NULL; char *newDecoder = NULL; char *newEncoder = NULL; char *localTitle = UTF8toCHAR(metadata_get_title(mdata), ICONV_REPLACE); char *localArtist = UTF8toCHAR(metadata_get_artist(mdata), ICONV_REPLACE); char *localMetaString = UTF8toCHAR(metadata_get_string(mdata), ICONV_REPLACE); decoder = xstrdup(getFormatDecoder(extension)); if (strlen(decoder) == 0) { printf("%s: Unknown extension '%s', cannot decode '%s'\n", __progname, extension, fileName); xfree(localTitle); xfree(localArtist); xfree(localMetaString); xfree(decoder); return (NULL); } newDecoder = replaceString(decoder, TRACK_PLACEHOLDER, fileName); if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) { char *tmpStr = replaceString(newDecoder, ARTIST_PLACEHOLDER, localArtist); xfree(newDecoder); newDecoder = tmpStr; } if (strstr(decoder, TITLE_PLACEHOLDER) != NULL) { char *tmpStr = replaceString(newDecoder, TITLE_PLACEHOLDER, localTitle); xfree(newDecoder); newDecoder = tmpStr; } /* * if meta * if (prog && format) * metatoformat * else * if (!prog && title) * emptymeta * else * replacemeta */ if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) { if (metadataFromProgram && pezConfig->metadataFormat != NULL) { char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata); char *tmpStr = replaceString(newDecoder, METADATA_PLACEHOLDER, mdataString); xfree(newDecoder); xfree(mdataString); newDecoder = tmpStr; } else { if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) { char *tmpStr = replaceString(newDecoder, METADATA_PLACEHOLDER, ""); xfree(newDecoder); newDecoder = tmpStr; } else { char *tmpStr = replaceString(newDecoder, METADATA_PLACEHOLDER, localMetaString); xfree(newDecoder); newDecoder = tmpStr; } } } encoder = xstrdup(getFormatEncoder(pezConfig->format)); if (strlen(encoder) == 0) { if (vFlag) printf("%s: Passing through%s%s data from the decoder\n", __progname, (strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ", pezConfig->format); commandStringLen = strlen(newDecoder) + 1; commandString = xcalloc(commandStringLen, sizeof(char)); strlcpy(commandString, newDecoder, commandStringLen); xfree(localTitle); xfree(localArtist); xfree(localMetaString); xfree(decoder); xfree(encoder); xfree(newDecoder); return (commandString); } newEncoder = replaceString(encoder, ARTIST_PLACEHOLDER, localArtist); if (strstr(encoder, TITLE_PLACEHOLDER) != NULL) { char *tmpStr = replaceString(newEncoder, TITLE_PLACEHOLDER, localTitle); xfree(newEncoder); newEncoder = tmpStr; } if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) { if (metadataFromProgram && pezConfig->metadataFormat != NULL) { char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata); char *tmpStr = replaceString(newEncoder, METADATA_PLACEHOLDER, mdataString); xfree(newEncoder); xfree(mdataString); newEncoder = tmpStr; } else { if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) { char *tmpStr = replaceString(newEncoder, METADATA_PLACEHOLDER, ""); xfree(newEncoder); newEncoder = tmpStr; } else { char *tmpStr = replaceString(newEncoder, METADATA_PLACEHOLDER, localMetaString); xfree(newEncoder); newEncoder = tmpStr; } } } commandStringLen = strlen(newDecoder) + strlen(" | ") + strlen(newEncoder) + 1; commandString = xcalloc(commandStringLen, sizeof(char)); snprintf(commandString, commandStringLen, "%s | %s", newDecoder, newEncoder); xfree(localTitle); xfree(localArtist); xfree(localMetaString); xfree(decoder); xfree(encoder); xfree(newDecoder); xfree(newEncoder); return (commandString); } char * getMetadataString(const char *format, metadata_t *mdata) { char *tmp, *str; if (mdata == NULL) { printf("%s: getMetadataString(): Internal error: NULL metadata_t\n", __progname); abort(); } if (format == NULL) return (NULL); str = xstrdup(format); if (strstr(format, ARTIST_PLACEHOLDER) != NULL) { tmp = replaceString(str, ARTIST_PLACEHOLDER, metadata_get_artist(mdata)); xfree(str); str = tmp; } if (strstr(format, TITLE_PLACEHOLDER) != NULL) { tmp = replaceString(str, TITLE_PLACEHOLDER, metadata_get_title(mdata)); xfree(str); str = tmp; } if (strstr(format, STRING_PLACEHOLDER) != NULL) { tmp = replaceString(str, STRING_PLACEHOLDER, metadata_get_string(mdata)); xfree(str); str = tmp; } if (strstr(format, TRACK_PLACEHOLDER) != NULL) { tmp = replaceString(str, TRACK_PLACEHOLDER, metadata_get_filename(mdata)); xfree(str); str = tmp; } return (str); } metadata_t * getMetadata(const char *fileName) { metadata_t *mdata; if (metadataFromProgram) { if ((mdata = metadata_program(fileName, nFlag)) == NULL) return (NULL); if (!metadata_program_update(mdata, METADATA_ALL)) { metadata_free(&mdata); return (NULL); } } else { if ((mdata = metadata_file(fileName, nFlag)) == NULL) return (NULL); if (!metadata_file_update(mdata)) { metadata_free(&mdata); return (NULL); } } return (mdata); } int setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy) { shout_metadata_t *shout_mdata = NULL; char *songInfo; const char *artist, *title; int ret = SHOUTERR_SUCCESS; if (shout == NULL) { printf("%s: setMetadata(): Internal error: NULL shout_t\n", __progname); abort(); } if (mFlag) return (SHOUTERR_SUCCESS); if (mdata == NULL) return 1; if ((shout_mdata = shout_metadata_new()) == NULL) { printf("%s: shout_metadata_new(): %s\n", __progname, strerror(ENOMEM)); exit(1); } artist = metadata_get_artist(mdata); title = metadata_get_title(mdata); /* * We can do this, because we know how libshout works. This adds * "charset=UTF-8" to the HTTP metadata update request and has the * desired effect of letting newer-than-2.3.1 versions of Icecast know * which encoding we're using. */ if (shout_metadata_add(shout_mdata, "charset", "UTF-8") != SHOUTERR_SUCCESS) { /* Assume SHOUTERR_MALLOC */ printf("%s: shout_metadata_add(): %s\n", __progname, strerror(ENOMEM)); exit(1); } if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) { if (artist[0] == '\0' && title[0] == '\0') songInfo = xstrdup(metadata_get_string(mdata)); else songInfo = metadata_assemble_string(mdata); if (artist[0] != '\0' && title[0] != '\0') { if (shout_metadata_add(shout_mdata, "artist", artist) != SHOUTERR_SUCCESS) { printf("%s: shout_metadata_add(): %s\n", __progname, strerror(ENOMEM)); exit(1); } if (shout_metadata_add(shout_mdata, "title", title) != SHOUTERR_SUCCESS) { printf("%s: shout_metadata_add(): %s\n", __progname, strerror(ENOMEM)); exit(1); } } else { if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) { printf("%s: shout_metadata_add(): %s\n", __progname, strerror(ENOMEM)); exit(1); } } } else if (shout_metadata_add(shout_mdata, "song", songInfo) != SHOUTERR_SUCCESS) { printf("%s: shout_metadata_add(): %s\n", __progname, strerror(ENOMEM)); exit(1); } if ((ret = shout_set_metadata(shout, shout_mdata)) != SHOUTERR_SUCCESS) printf("%s: shout_set_metadata(): %s\n", __progname, shout_get_error(shout)); shout_metadata_free(shout_mdata); if (ret == SHOUTERR_SUCCESS) { if (mdata_copy != NULL && *mdata_copy == NULL) *mdata_copy = xstrdup(songInfo); } xfree(songInfo); return (ret); } FILE * openResource(shout_t *shout, const char *fileName, int *popenFlag, metadata_t **mdata_p, int *isStdin, long *songLen) { FILE *filep = NULL; char extension[25]; char *p = NULL; char *pCommandString = NULL; metadata_t *mdata; if (mdata_p != NULL) *mdata_p = NULL; if (songLen != NULL) *songLen = 0; if (strcmp(fileName, "stdin") == 0) { if (metadataFromProgram) { if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL) return (NULL); if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) { metadata_free(&mdata); return (NULL); } if (mdata_p != NULL) *mdata_p = mdata; else metadata_free(&mdata); } if (isStdin != NULL) *isStdin = 1; #ifdef WIN32 _setmode(_fileno(stdin), _O_BINARY); #endif filep = stdin; return (filep); } if (isStdin != NULL) *isStdin = 0; extension[0] = '\0'; 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) { printf("%s: Error: Cannot determine file type of '%s'\n", __progname, fileName); return (filep); } if (metadataFromProgram) { if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL) return (NULL); } else { if ((mdata = getMetadata(fileName)) == NULL) return (NULL); } if (songLen != NULL) *songLen = metadata_get_length(mdata); *popenFlag = 0; if (pezConfig->reencode) { int stderr_fd = -1; pCommandString = buildCommandString(extension, fileName, mdata); if (mdata_p != NULL) *mdata_p = mdata; else metadata_free(&mdata); if (vFlag > 1) printf("%s: Running command `%s`\n", __progname, pCommandString); if (qFlag) { int fd; stderr_fd = dup(fileno(stderr)); if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) == -1) { printf("%s: Cannot open %s for redirecting STDERR output: %s\n", __progname, _PATH_DEVNULL, strerror(errno)); exit(1); } dup2(fd, fileno(stderr)); if (fd > 2) close(fd); } fflush(NULL); errno = 0; if ((filep = popen(pCommandString, "r")) == NULL) { printf("%s: popen(): Error while executing '%s'", __progname, pCommandString); /* popen() does not set errno reliably ... */ if (errno) printf(": %s\n", strerror(errno)); else printf("\n"); } else { *popenFlag = 1; #ifdef WIN32 _setmode(_fileno(filep), _O_BINARY ); #endif } xfree(pCommandString); if (qFlag) dup2(stderr_fd, fileno(stderr)); if (stderr_fd > 2) close(stderr_fd); return (filep); } if (mdata_p != NULL) *mdata_p = mdata; else metadata_free(&mdata); if ((filep = fopen(fileName, "rb")) == NULL) printf("%s: %s: %s\n", __progname, fileName, strerror(errno)); return (filep); } int reconnectServer(shout_t *shout, int closeConn) { unsigned int i; int close_conn = closeConn; printf("%s: Connection to %s lost\n", __progname, pezConfig->URL); i = 0; while (++i) { printf("%s: Attempting reconnection #", __progname); if (pezConfig->reconnectAttempts > 0) printf("%u/%u: ", i, pezConfig->reconnectAttempts); else printf("%u: ", i); if (close_conn == 0) close_conn = 1; else shout_close(shout); if (shout_open(shout) == SHOUTERR_SUCCESS) { printf("OK\n%s: Reconnect to %s successful\n", __progname, pezConfig->URL); return (1); } printf("FAILED: %s\n", shout_get_error(shout)); if (pezConfig->reconnectAttempts > 0 && i >= pezConfig->reconnectAttempts) break; printf("%s: Waiting 5s for %s to come back ...\n", __progname, pezConfig->URL); if (quit) return (0); else sleep(5); }; printf("%s: Giving up\n", __progname); return (0); } const char * getTimeString(long seconds) { static char str[20]; long secs, mins, hours; if (seconds < 0) return (NULL); secs = seconds; hours = secs / 3600; secs %= 3600; mins = secs / 60; secs %= 60; snprintf(str, sizeof(str), "%ldh%02ldm%02lds", hours, mins, secs); return ((const char *)str); } int sendStream(shout_t *shout, FILE *filepstream, const char *fileName, int isStdin, const char *songLenStr, struct timeval *tv) { unsigned char buff[4096]; size_t bytes_read, total, oldTotal; int ret; double kbps = -1.0; struct timeval timeStamp, *startTime = tv; struct timeval callTime, currentTime; if (startTime == NULL) { printf("%s: sendStream(): Internal error: startTime is NULL\n", __progname); abort(); } ez_gettimeofday((void *)&callTime); timeStamp.tv_sec = startTime->tv_sec; timeStamp.tv_usec = startTime->tv_usec; total = oldTotal = 0; ret = STREAM_DONE; while ((bytes_read = fread(buff, 1UL, sizeof(buff), filepstream)) > 0) { if (shout_get_connected(shout) != SHOUTERR_CONNECTED && reconnectServer(shout, 0) == 0) { ret = STREAM_SERVERR; break; } shout_sync(shout); if (shout_send(shout, buff, bytes_read) != SHOUTERR_SUCCESS) { printf("%s: shout_send(): %s\n", __progname, shout_get_error(shout)); if (reconnectServer(shout, 1)) break; else { ret = STREAM_SERVERR; break; } } if (quit) break; if (rereadPlaylist_notify) { rereadPlaylist_notify = 0; if (!pezConfig->fileNameIsProgram) printf("%s: SIGHUP signal received, will reread playlist after this file\n", __progname); } if (skipTrack) { skipTrack = 0; ret = STREAM_SKIP; break; } ez_gettimeofday((void *)¤tTime); if (queryMetadata || (pezConfig->metadataRefreshInterval != -1 && (currentTime.tv_sec - callTime.tv_sec >= pezConfig->metadataRefreshInterval) ) ) { queryMetadata = 0; if (metadataFromProgram) { ret = STREAM_UPDMDATA; break; } } total += bytes_read; if (qFlag && vFlag) { double oldTime, newTime; if (!isStdin && playlistMode) { if (pezConfig->fileNameIsProgram) { char *tmp = xstrdup(pezConfig->fileName); printf(" [%s]", local_basename(tmp)); xfree(tmp); } else printf(" [%4lu/%-4lu]", playlist_get_position(playlist), playlist_get_num_items(playlist)); } oldTime = (double)timeStamp.tv_sec + (double)timeStamp.tv_usec / 1000000.0; newTime = (double)currentTime.tv_sec + (double)currentTime.tv_usec / 1000000.0; if (songLenStr == NULL) printf(" [ %s]", getTimeString(currentTime.tv_sec - startTime->tv_sec)); else printf(" [ %s/%s]", getTimeString(currentTime.tv_sec - startTime->tv_sec), songLenStr); if (newTime - oldTime >= 1.0) { kbps = (((double)(total - oldTotal) / (newTime - oldTime)) * 8.0) / 1000.0; timeStamp.tv_sec = currentTime.tv_sec; timeStamp.tv_usec = currentTime.tv_usec; oldTotal = total; } if (kbps < 0) printf(" "); else printf(" [%8.2f kbps]", kbps); printf(" \r"); fflush(stdout); } } if (ferror(filepstream)) { if (errno == EINTR) { clearerr(filepstream); ret = STREAM_CONT; } else if (errno == EBADF && isStdin) printf("%s: No (more) data available on standard input\n", __progname); else printf("%s: sendStream(): Error while reading '%s': %s\n", __progname, fileName, strerror(errno)); } return (ret); } int streamFile(shout_t *shout, const char *fileName) { FILE *filepstream = NULL; int popenFlag = 0; char *songLenStr = NULL; int isStdin = 0; int ret, retval = 0; long songLen; metadata_t *mdata; struct timeval startTime; if ((filepstream = openResource(shout, fileName, &popenFlag, &mdata, &isStdin, &songLen)) == NULL) { if (++resource_errors > 100) { printf("%s: Too many errors -- giving up.\n", __progname); return (0); } /* Continue with next resource on failure: */ return (1); } resource_errors = 0; if (mdata != NULL) { char *tmp, *metaData; tmp = metadata_assemble_string(mdata); if ((metaData = UTF8toCHAR(tmp, ICONV_REPLACE)) == NULL) metaData = xstrdup("(unknown title)"); xfree(tmp); printf("%s: Streaming ``%s''", __progname, metaData); if (vFlag) printf(" (file: %s)\n", fileName); else printf("\n"); xfree(metaData); /* MP3 streams are special, so set the metadata explicitly: */ if (strcmp(pezConfig->format, MP3_FORMAT) == 0) setMetadata(shout, mdata, NULL); metadata_free(&mdata); } else if (isStdin) printf("%s: Streaming from standard input\n", __progname); if (songLen > 0) songLenStr = xstrdup(getTimeString(songLen)); ez_gettimeofday((void *)&startTime); do { ret = sendStream(shout, filepstream, fileName, isStdin, songLenStr, &startTime); if (quit) break; if (ret != STREAM_DONE) { if ((skipTrack && rereadPlaylist) || (skipTrack && queryMetadata)) { skipTrack = 0; ret = STREAM_CONT; } if (queryMetadata && rereadPlaylist) { queryMetadata = 0; ret = STREAM_CONT; } if (ret == STREAM_SKIP || skipTrack) { skipTrack = 0; if (!isStdin && vFlag) printf("%s: SIGUSR1 signal received, skipping current track\n", __progname); retval = 1; ret = STREAM_DONE; } if (ret == STREAM_UPDMDATA || queryMetadata) { queryMetadata = 0; if (mFlag) continue; if (metadataFromProgram) { char *mdataStr = NULL; metadata_t *prog_mdata; if (vFlag > 1) printf("%s: Querying '%s' for fresh metadata\n", __progname, pezConfig->metadataProgram); if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) { retval = 0; ret = STREAM_DONE; continue; } if (setMetadata(shout, prog_mdata, &mdataStr) != SHOUTERR_SUCCESS) { retval = 0; ret = STREAM_DONE; continue; } metadata_free(&prog_mdata); if (vFlag > 1) printf("%s: New metadata: ``%s''\n", __progname, mdataStr); xfree(mdataStr); } } if (ret == STREAM_SERVERR) { retval = 0; ret = STREAM_DONE; } } else retval = 1; } while (ret != STREAM_DONE); if (popenFlag) pclose(filepstream); else if (!isStdin) fclose(filepstream); if (songLenStr != NULL) xfree(songLenStr); return (retval); } int streamPlaylist(shout_t *shout, const char *fileName) { const char *song; char lastSong[PATH_MAX]; if (playlist == NULL) { if (pezConfig->fileNameIsProgram) { if ((playlist = playlist_program(fileName)) == NULL) return (0); } else { if ((playlist = playlist_read(fileName)) == NULL) return (0); if (vFlag && playlist_get_num_items(playlist) == 0) printf("%s: Warning: Playlist '%s' is empty\n", __progname, fileName); } } else { /* * XXX: This preserves traditional behavior, however, * rereading the playlist after each walkthrough seems a * bit more logical. */ playlist_rewind(playlist); } if (!pezConfig->fileNameIsProgram && pezConfig->shuffle) playlist_shuffle(playlist); while ((song = playlist_get_next(playlist)) != NULL) { strlcpy(lastSong, song, sizeof(lastSong)); if (!streamFile(shout, song)) return (0); if (quit) break; if (rereadPlaylist) { rereadPlaylist = rereadPlaylist_notify = 0; if (pezConfig->fileNameIsProgram) continue; printf("%s: Rereading playlist\n", __progname); if (!playlist_reread(&playlist)) return (0); if (pezConfig->shuffle) playlist_shuffle(playlist); else { playlist_goto_entry(playlist, lastSong); playlist_skip_next(playlist); } continue; } } return (1); } /* * Borrowed from OpenNTPd-portable's compat-openbsd/bsd-misc.c. * Does not use xalloc on purpose, as the 9 bytes of memory that don't get * cleaned up in the end really don't matter. */ char * getProgname(const char *argv0) { #ifdef HAVE___PROGNAME (void)argv0; return (strdup(__progname)); #else char *p; if (argv0 == NULL) return ((char *)"ezstream"); p = strrchr(argv0, path_separators[0]); if (p == NULL) p = (char *)argv0; else p++; return (strdup(p)); #endif /* HAVE___PROGNAME */ } int ez_shutdown(int exitval) { shout_shutdown(); playlist_shutdown(); freeConfig(pezConfig); xalloc_shutdown(); return (exitval); } void usage(void) { printf("usage: %s [-hmnqVv] -c configfile\n", __progname); printf(" %s -s [playlist]\n", __progname); } void usageHelp(void) { printf("\n"); printf(" -c configfile use XML configuration in configfile (mandatory)\n"); printf(" -h display this additional help and exit\n"); printf(" -m disable metadata updates\n"); printf(" -n normalize metadata strings\n"); printf(" -q suppress STDERR output from external en-/decoders\n"); printf(" -s [playlist] read lines from playlist (or STDIN), shuffle and print them to\n"); printf(" STDOUT, then exit\n"); printf(" -V print the version number and exit\n"); printf(" -v verbose output (use twice for more effect)\n"); printf("\n"); printf("See the ezstream(1) manual for detailed information.\n"); } int main(int argc, char *argv[]) { int c; char *configFile = NULL; char *host = NULL; unsigned short port = 0; char *mount = NULL; shout_t *shout; extern char *optarg; extern int optind; #ifdef HAVE_SIGNALS struct sigaction act; unsigned int i; #endif #ifdef XALLOC_DEBUG xalloc_initialize_debug(2, NULL); #else xalloc_initialize(); #endif /* XALLOC_DEBUG */ playlist_init(); shout_init(); __progname = getProgname(argv[0]); pezConfig = getEZConfig(); mFlag = 0; nFlag = 0; qFlag = 0; vFlag = 0; while ((c = getopt(argc, argv, "c:hmnqsVv")) != -1) { switch (c) { case 'c': if (configFile != NULL) { printf("Error: multiple -c arguments given\n"); usage(); return (ez_shutdown(2)); } configFile = xstrdup(optarg); break; case 'h': usage(); usageHelp(); return (ez_shutdown(0)); case 'm': mFlag = 1; break; case 'n': nFlag = 1; break; case 'q': qFlag = 1; break; case 's': sFlag = 1; break; case 'V': printf("%s\n", PACKAGE_STRING); return (ez_shutdown(0)); case 'v': vFlag++; break; case '?': usage(); return (ez_shutdown(2)); default: break; } } argc -= optind; argv += optind; if (sFlag) { playlist_t *pl; const char *entry; switch (argc) { case 0: pl = playlist_read(NULL); if (pl == NULL) return (ez_shutdown(1)); break; case 1: pl = playlist_read(argv[0]); if (pl == NULL) return (ez_shutdown(1)); break; default: printf("Error: Too many arguments.\n"); return (ez_shutdown(2)); } playlist_shuffle(pl); while ((entry = playlist_get_next(pl)) != NULL) printf("%s\n", entry); playlist_free(&pl); return (ez_shutdown(0)); } if (configFile == NULL) { printf("You must supply a config file with the -c argument.\n"); usage(); return (ez_shutdown(2)); } else { /* * Attempt to open configFile here for a more meaningful error * message. Where possible, do it with stat() and check for * safe config file permissions. */ #ifdef HAVE_STAT struct stat st; if (stat(configFile, &st) == -1) { printf("%s: %s\n", configFile, strerror(errno)); usage(); return (ez_shutdown(2)); } if (vFlag && (st.st_mode & (S_IRGRP | S_IROTH))) printf("%s: Warning: %s is group and/or world readable\n", __progname, configFile); if (st.st_mode & (S_IWGRP | S_IWOTH)) { printf("%s: Error: %s is group and/or world writeable\n", __progname, configFile); return (ez_shutdown(2)); } #else FILE *tmp; if ((tmp = fopen(configFile, "r")) == NULL) { printf("%s: %s\n", configFile, strerror(errno)); usage(); return (ez_shutdown(2)); } fclose(tmp); #endif /* HAVE_STAT */ } if (!parseConfig(configFile)) return (ez_shutdown(2)); if (pezConfig->URL == NULL) { printf("%s: Error: Missing \n", configFile); return (ez_shutdown(2)); } if (!urlParse(pezConfig->URL, &host, &port, &mount)) { printf("Must be of the form ``http://server:port/mountpoint''\n"); return (ez_shutdown(2)); } if (strlen(host) == 0) { printf("%s: Error: Invalid : Missing server:\n", configFile); printf("Must be of the form ``http://server:port/mountpoint''\n"); return (ez_shutdown(2)); } if (strlen(mount) == 0) { printf("%s: Error: Invalid : Missing mountpoint:\n", configFile); printf("Must be of the form ``http://server:port/mountpoint''\n"); return (ez_shutdown(2)); } if (pezConfig->password == NULL) { printf("%s: Error: Missing \n", configFile); return (ez_shutdown(2)); } if (pezConfig->fileName == NULL) { printf("%s: Error: Missing \n", configFile); return (ez_shutdown(2)); } if (pezConfig->format == NULL) { printf("%s: Warning: Missing :\n", configFile); printf("Specify a stream format of either MP3, VORBIS or THEORA\n"); } xfree(configFile); if ((shout = stream_setup(host, port, mount)) == NULL) return (ez_shutdown(1)); if (pezConfig->metadataProgram != NULL) metadataFromProgram = 1; else metadataFromProgram = 0; #ifdef HAVE_SIGNALS memset(&act, 0, sizeof(act)); act.sa_handler = sig_handler; # ifdef SA_RESTART act.sa_flags = SA_RESTART; # endif for (i = 0; i < sizeof(ezstream_signals) / sizeof(int); i++) { if (sigaction(ezstream_signals[i], &act, NULL) == -1) { printf("%s: sigaction(): %s\n", __progname, strerror(errno)); return (ez_shutdown(1)); } } /* * Ignore SIGPIPE, which has been seen to give a long-running ezstream * process trouble. EOF and/or EPIPE are also easier to handle. */ act.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &act, NULL) == -1) { printf("%s: sigaction(): %s\n", __progname, strerror(errno)); return (ez_shutdown(1)); } #endif /* HAVE_SIGNALS */ if (shout_open(shout) == SHOUTERR_SUCCESS) { int ret; printf("%s: Connected to http://%s:%hu%s\n", __progname, host, port, mount); if (pezConfig->fileNameIsProgram || strrcasecmp(pezConfig->fileName, ".m3u") == 0 || strrcasecmp(pezConfig->fileName, ".txt") == 0) playlistMode = 1; else playlistMode = 0; if (vFlag && pezConfig->fileNameIsProgram) printf("%s: Using program '%s' to get filenames for streaming\n", __progname, pezConfig->fileName); do { if (playlistMode) { ret = streamPlaylist(shout, pezConfig->fileName); } else { ret = streamFile(shout, pezConfig->fileName); } if (quit) break; if (pezConfig->streamOnce) break; } while (ret); shout_close(shout); } else printf("%s: Connection to http://%s:%hu%s failed: %s\n", __progname, host, port, mount, shout_get_error(shout)); if (quit) printf("\r%s: SIGINT or SIGTERM received\n", __progname); if (vFlag) printf("%s: Exiting ...\n", __progname); xfree(host); xfree(mount); playlist_free(&playlist); return (ez_shutdown(0)); }