/* * Copyright (c) 2018, 2020 Moritz Grimm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "compat.h" #if defined(HAVE_LIBGEN_H) && !defined(__linux__) # include #endif /* HAVE_LIBGEN_H && !__linux__ */ #include #include #include #include #include "cfg.h" #include "cfgfile_xml.h" #include "ezconfig0.h" #include "log.h" #include "util.h" #include "xalloc.h" static char *v0_cfgfile; static unsigned int cfg_verbosity; #define OPTSTRING "0:hv" enum opt_vals { OPT_V0CONFIGFILE = '0', OPT_HELP = 'h', OPT_VERBOSE = 'v', OPT_INVALID = '?' }; static void _usage(void); static void _usage_help(void); static int _cmdline_parse(int, char *[], int *); static int _url_parse(const char *, char **, char **, char **); static int _parse_ezconfig0(EZCONFIG *); static void _usage(void) { fprintf(stderr, "usage: %s [-hv] -0 v0-cfgfile\n", util_get_progname(NULL)); } static void _usage_help(void) { fprintf(stderr, "\n"); fprintf(stderr, " -0 v0-cfgfile migrate from v0-cfgfile (ezstream version 0.x)\n"); fprintf(stderr, " -h print this help and exit\n"); fprintf(stderr, " -v increase logging verbosity\n"); } static int _cmdline_parse(int argc, char *argv[], int *ret_p) { v0_cfgfile = NULL; cfg_verbosity = 0; optind = 1; for (;;) { int ch; ch = getopt(argc, argv, OPTSTRING); if (0 > ch) break; switch (ch) { case OPT_V0CONFIGFILE: v0_cfgfile = optarg; break; case OPT_HELP: _usage(); _usage_help(); *ret_p = 0; return (-1); case OPT_VERBOSE: cfg_verbosity++; break; case OPT_INVALID: default: _usage(); *ret_p = 2; return (-1); } } if (!v0_cfgfile) { fprintf(stderr, "-%c must be provided\n", OPT_V0CONFIGFILE); _usage(); *ret_p = 2; return (-1); } return (0); } static int _url_parse(const char *url, char **hostname, char **port, char **mountname) { const char *p1, *p2, *p3; size_t hostsiz, portsiz, mountsiz; if (strncasecmp(url, "http://", strlen("http://")) != 0) { log_error("%s: Invalid : Not an HTTP address", v0_cfgfile); return (-1); } p1 = url + strlen("http://"); p2 = strchr(p1, ':'); if (p2 == NULL) { log_error("%s: Invalid : Missing port", v0_cfgfile); return (-1); } hostsiz = (size_t)(p2 - p1) + 1; *hostname = xmalloc(hostsiz); strlcpy(*hostname, p1, hostsiz); p2++; p3 = strchr(p2, '/'); if (p3 == NULL) { log_error("%s: Invalid : Missing mountpoint or too long port number", v0_cfgfile); xfree(*hostname); return (-1); } portsiz = (size_t)(p3 - p2) + 1; *port = xmalloc(portsiz); strlcpy(*port, p2, portsiz); mountsiz = strlen(p3) + 1; *mountname = xmalloc(mountsiz); strlcpy(*mountname, p3, mountsiz); return (0); } #define ENTITY_SET(e, el, func, ctx, val) do { \ const char *err_str2; \ \ if (0 > (func)((e), (el), (val), &err_str2)) { \ log_warning("%s: %s: %s: %s", v0_cfgfile, (ctx), \ err_str2, (val)); \ warnings++; \ } \ } while (0) static int _parse_ezconfig0(EZCONFIG *ez) { char *hostname, *port, *mountname; const char *err_str; int warnings = 0; cfg_server_list_t srv_list = cfg_get_servers(); cfg_server_t srv = cfg_server_list_get(srv_list, CFG_DEFAULT); cfg_stream_list_t str_list = cfg_get_streams(); cfg_stream_t str = cfg_stream_list_get(str_list, CFG_DEFAULT); cfg_intake_list_t in_list = cfg_get_intakes(); cfg_intake_t in = cfg_intake_list_get(in_list, CFG_DEFAULT); cfg_encoder_list_t enc_list = cfg_get_encoders(); cfg_decoder_list_t dec_list = cfg_get_decoders();; unsigned int i; char strbuf[BUFSIZ]; if (NULL == ez->URL || 0 == strlen(ez->URL)) { log_error("%s: Missing -- not an ezstream version 0.x configuration?", v0_cfgfile); return (-1); } if (0 > _url_parse(ez->URL, &hostname, &port, &mountname)) return (-1); ENTITY_SET(srv, srv_list, cfg_server_set_protocol, "", "HTTP"); ENTITY_SET(srv, srv_list, cfg_server_set_hostname, "", hostname); xfree(hostname); ENTITY_SET(srv, srv_list, cfg_server_set_port, "", port); xfree(port); ENTITY_SET(str, str_list, cfg_stream_set_mountpoint, "", mountname); xfree(mountname); if (ez->username) ENTITY_SET(srv, srv_list, cfg_server_set_user, "", ez->username); if (ez->password) ENTITY_SET(srv, srv_list, cfg_server_set_password, "", ez->password); if (ez->format) { if (0 == strcasecmp(ez->format, "vorbis") || 0 == strcasecmp(ez->format, "theora")) ENTITY_SET(str, str_list, cfg_stream_set_format, "", "Ogg"); else ENTITY_SET(str, str_list, cfg_stream_set_format, "", ez->format); } if (ez->fileName) { if (0 == strcasecmp(ez->fileName, "stdin")) ENTITY_SET(in, in_list, cfg_intake_set_type, "", "stdin"); else ENTITY_SET(in, in_list, cfg_intake_set_filename, "", ez->fileName); } if (ez->metadataProgram) { if (0 > cfg_set_metadata_program(ez->metadataProgram, &err_str)) { log_warning("%s: %s: %s: %s", v0_cfgfile, "", err_str, ez->metadataProgram); warnings++; } } if (ez->metadataFormat) { if (0 > cfg_set_metadata_format_str(ez->metadataFormat, &err_str)) { log_warning("%s: %s: %s: %s", v0_cfgfile, "", err_str, ez->metadataFormat); warnings++; } } if (ez->metadataRefreshInterval) { snprintf(strbuf, sizeof(strbuf), "%d", ez->metadataRefreshInterval); if (0 > cfg_set_metadata_refresh_interval(strbuf, &err_str)) { log_warning("%s: %s: %s: %s", v0_cfgfile, "", err_str, strbuf); warnings++; } } if (ez->fileNameIsProgram) ENTITY_SET(in, in_list, cfg_intake_set_type, "playlist_program", "program"); snprintf(strbuf, sizeof(strbuf), "%d", ez->shuffle); ENTITY_SET(in, in_list, cfg_intake_set_shuffle, "", strbuf); snprintf(strbuf, sizeof(strbuf), "%d", ez->streamOnce); ENTITY_SET(in, in_list, cfg_intake_set_stream_once, "", strbuf); snprintf(strbuf, sizeof(strbuf), "%u", ez->reconnectAttempts); ENTITY_SET(srv, srv_list, cfg_server_set_reconnect_attempts, "", strbuf); if (ez->serverName) ENTITY_SET(str, str_list, cfg_stream_set_stream_name, "", ez->serverName); if (ez->serverURL) ENTITY_SET(str, str_list, cfg_stream_set_stream_url, "", ez->serverURL); if (ez->serverGenre) ENTITY_SET(str, str_list, cfg_stream_set_stream_genre, "", ez->serverGenre); if (ez->serverDescription) ENTITY_SET(str, str_list, cfg_stream_set_stream_description, "", ez->serverDescription); if (ez->serverBitrate) ENTITY_SET(str, str_list, cfg_stream_set_stream_bitrate, "", ez->serverBitrate); if (ez->serverChannels) ENTITY_SET(str, str_list, cfg_stream_set_stream_channels, "", ez->serverChannels); if (ez->serverSamplerate) ENTITY_SET(str, str_list, cfg_stream_set_stream_samplerate, "", ez->serverSamplerate); if (ez->serverQuality) ENTITY_SET(str, str_list, cfg_stream_set_stream_quality, "", ez->serverQuality); ENTITY_SET(str, str_list, cfg_stream_set_public, "", ez->serverPublic ? "yes" : "no"); if (ez->reencode) { if (0 == strcasecmp(ez->format, "vorbis") || 0 == strcasecmp(ez->format, "theora")) ENTITY_SET(str, str_list, cfg_stream_set_encoder, "", "Ogg"); else ENTITY_SET(str, str_list, cfg_stream_set_encoder, "", ez->format); } for (i = 0; i < MAX_FORMAT_ENCDEC; i++) { FORMAT_ENCDEC *ed = ez->encoderDecoders[i]; if (!ed) continue; if (ed->encoder) { cfg_encoder_t enc = cfg_encoder_list_get(enc_list, CFG_DEFAULT); ENTITY_SET(enc, enc_list, cfg_encoder_set_program, "", ed->encoder); if (ed->format) { if (0 == strcasecmp(ed->format, "vorbis") || 0 == strcasecmp(ed->format, "theora")) { ENTITY_SET(enc, enc_list, cfg_encoder_set_name, " (encoder)", "Ogg"); ENTITY_SET(enc, enc_list, cfg_encoder_set_format_str, " (encoder)", "Ogg"); } else { ENTITY_SET(enc, enc_list, cfg_encoder_set_name, " (encoder)", ed->format); ENTITY_SET(enc, enc_list, cfg_encoder_set_format_str, " (encoder)", ed->format); } } if (0 > cfg_encoder_validate(enc, &err_str)) { log_warning("%s: %s: %s", v0_cfgfile, " (encoder)", err_str); cfg_encoder_list_remove(enc_list, &enc); warnings++; } } if (ed->decoder) { cfg_decoder_t dec = NULL; if (ed->format) { if (0 == strcasecmp(ed->format, "vorbis") || 0 == strcasecmp(ed->format, "theora")) dec = cfg_decoder_list_find(dec_list, "Ogg"); else dec = cfg_decoder_list_find(dec_list, ed->format); } if (NULL == dec) dec = cfg_decoder_list_get(dec_list, CFG_DEFAULT); ENTITY_SET(dec, dec_list, cfg_decoder_set_program, "", ed->decoder); if (ed->format) { if (0 == strcasecmp(ed->format, "vorbis") || 0 == strcasecmp(ed->format, "theora")) ENTITY_SET(dec, dec_list, cfg_decoder_set_name, " (decoder)", "Ogg"); else ENTITY_SET(dec, dec_list, cfg_decoder_set_name, " (decoder)", ed->format); } if (ed->match) ENTITY_SET(dec, dec_list, cfg_decoder_add_match, "", ed->match); if (0 > cfg_decoder_validate(dec, &err_str)) { log_warning("%s: %s: %s", v0_cfgfile, " (decoder)", err_str); cfg_decoder_list_remove(dec_list, &dec); warnings++; } } } if (warnings) log_warning("%s: %u warnings", v0_cfgfile, warnings); if (cfg_stream_get_encoder(str) && NULL == cfg_encoder_list_find(enc_list, cfg_stream_get_encoder(str))) { log_warning("%s: %s encoder not found; disabling reencoding", v0_cfgfile, cfg_stream_get_encoder(str)); cfg_stream_set_encoder(str, NULL, NULL, NULL); } if (0 > cfg_server_validate(srv, &err_str) || 0 > cfg_stream_validate(str, &err_str) || 0 > cfg_intake_validate(in, &err_str)) { log_error("%s: configuration invalid: %s", v0_cfgfile, err_str); return (-1); } return (0); } int main(int argc, char *argv[]) { int ret; EZCONFIG *ez = NULL; ret = 1; if (0 > cfg_init() || 0 > log_init(util_get_progname(argv[0])) || 0 > _cmdline_parse(argc, argv, &ret)) return (ret); (void)cfg_set_program_name(util_get_progname(argv[0]), NULL); if (0 > parseConfig(v0_cfgfile)) goto error; ez = getEZConfig(); if (0 > _parse_ezconfig0(ez)) goto error; printf("\n\n"); printf("\n\n"); cfgfile_xml_print(stdout); freeConfig(ez); log_exit(); cfg_exit(); return (0); error: if (ez) freeConfig(ez); log_exit(); cfg_exit(); return (1); }