1
0
mirror of https://gitlab.xiph.org/xiph/icecast-server.git synced 2025-01-03 14:56:34 -05:00

Merge branch 'update-dumpfile' into devel

This commit is contained in:
Philipp Schafft 2022-03-22 17:40:05 +00:00
commit b49602dbce
13 changed files with 172 additions and 65 deletions

View File

@ -31,4 +31,5 @@ nobase_dist_admin_DATA = \
includes/web-page.xsl \
ui/confirmdeleteuser.xsl \
ui/confirmkillclient.xsl \
ui/confirmkillsource.xsl
ui/confirmkillsource.xsl \
ui/confirmkilldumpfile.xsl

View File

@ -8,6 +8,14 @@
<li><a href="/admin/moveclients.xsl?mount={$mount}">Move listeners</a></li>
<li><a href="/admin/updatemetadata.xsl?mount={$mount}">Metadata</a></li>
<li><a href="/admin/fallbacks.xsl?mount={$mount}&amp;omode=strict">Set fallback</a></li>
<xsl:choose>
<xsl:when test="dumpfile_written/text() != '0'">
<li class="critical"><a href="/admin/ui/confirmkilldumpfile.xsl?mount={$mount}">Stop dumpfile</a></li>
</xsl:when>
<xsl:otherwise>
<li class="disabled"><a>No dumpfile running</a></li>
</xsl:otherwise>
</xsl:choose>
<li class="critical"><a href="/admin/ui/confirmkillsource.xsl?mount={$mount}">Kill source</a></li>
</ul>
</div>

View File

@ -0,0 +1,24 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:xt="http://www.jclark.com/xt" extension-element-prefixes="xt" exclude-result-prefixes="xt">
<!-- Import include files -->
<xsl:include href="includes/confirm.xsl"/>
<xsl:variable name="title">Confirm Stopping dumpfile</xsl:variable>
<xsl:template name="content">
<xsl:for-each select="/report/incident">
<xsl:variable name="get-parameters" select="resource[@type='parameter']/value[@member='get-parameters']" />
<xsl:variable name="mount" select="$get-parameters/value[@member='mount']" />
<xsl:call-template name="confirm">
<xsl:with-param name="text">Please confirm stopping the dumpfile for <code><xsl:value-of select="$mount/@value" /></code>.</xsl:with-param>
<xsl:with-param name="action-cancel">/admin/streamlist.xsl</xsl:with-param>
<xsl:with-param name="action-confirm">/admin/dumpfilecontrol.xsl</xsl:with-param>
<xsl:with-param name="params-cancel">
<xsl:copy-of select="$mount" />
</xsl:with-param>
<xsl:with-param name="params-confirm">
<xsl:copy-of select="$mount" />
<value member="action" value="kill" />
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

View File

@ -143,6 +143,8 @@
#define KILLSOURCE_RAW_REQUEST "killsource"
#define KILLSOURCE_HTML_REQUEST "killsource.xsl"
#define KILLSOURCE_JSON_REQUEST "killsource.json"
#define DUMPFILECONTROL_RAW_REQUEST "dumpfilecontrol"
#define DUMPFILECONTROL_HTML_REQUEST "dumpfilecontrol.xsl"
#define ADMIN_XSL_RESPONSE "response.xsl"
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_HTML_REQUEST "manageauth.xsl"
@ -190,6 +192,7 @@ static void command_list_listen_sockets (client_t *client, source_t *source, adm
static void command_move_clients (client_t *client, source_t *source, admin_format_t response);
static void command_kill_client (client_t *client, source_t *source, admin_format_t response);
static void command_kill_source (client_t *client, source_t *source, admin_format_t response);
static void command_dumpfile_control (client_t *client, source_t *source, admin_format_t response);
static void command_manageauth (client_t *client, source_t *source, admin_format_t response);
static void command_updatemetadata (client_t *client, source_t *source, admin_format_t response);
static void command_buildm3u (client_t *client, source_t *source, admin_format_t response);
@ -237,6 +240,8 @@ static const admin_command_handler_t handlers[] = {
{ KILLSOURCE_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, ADMINSAFE_UNSAFE, command_kill_source, NULL},
{ KILLSOURCE_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, ADMINSAFE_UNSAFE, command_kill_source, NULL},
{ KILLSOURCE_JSON_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_JSON, ADMINSAFE_UNSAFE, command_kill_source, NULL},
{ DUMPFILECONTROL_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, ADMINSAFE_UNSAFE, command_dumpfile_control, NULL},
{ DUMPFILECONTROL_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, ADMINSAFE_UNSAFE, command_dumpfile_control, NULL},
{ MANAGEAUTH_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, ADMINSAFE_HYBRID, command_manageauth, NULL},
{ MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, ADMINSAFE_HYBRID, command_manageauth, NULL},
{ MANAGEAUTH_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, ADMINSAFE_HYBRID, command_manageauth, NULL},
@ -543,6 +548,9 @@ xmlDocPtr admin_build_sourcelist(const char *mount, client_t *client, admin_form
xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"),
XMLSTR(source->format->contenttype));
}
snprintf(buf, sizeof(buf), "%"PRIu64, source->dumpfile_written);
xmlNewTextChild(srcnode, NULL, XMLSTR("dumpfile_written"), XMLSTR(buf));
}
node = avl_get_next(node);
}
@ -1116,6 +1124,19 @@ static void command_kill_source(client_t *client,
admin_send_response_simple(client, source, response, "Source Removed", 1);
}
static void command_dumpfile_control (client_t *client, source_t *source, admin_format_t response)
{
const char *action;
COMMAND_REQUIRE(client, "action", action);
if (strcmp(action, "kill") == 0) {
source_kill_dumpfile(source);
admin_send_response_simple(client, source, response, "Dumpfile killed.", 1);
} else {
admin_send_response_simple(client, source, response, "No such action", 0);
}
}
static void command_kill_client(client_t *client,
source_t *source,
admin_format_t response)

View File

@ -1737,6 +1737,14 @@ static void _parse_mount(xmlDocPtr doc,
} else if (xmlStrcmp(node->name, XMLSTR("dump-file")) == 0) {
mount->dumpfile = (char *)xmlNodeListGetString(doc,
node->xmlChildrenNode, 1);
} else if (xmlStrcmp(node->name, XMLSTR("dump-file-size-limit")) == 0) {
unsigned int val = mount->dumpfile_size_limit;
__read_unsigned_int(configuration, doc, node, &val, 0, UINT_MAX);
mount->dumpfile_size_limit = val;
} else if (xmlStrcmp(node->name, XMLSTR("dump-file-time-limit")) == 0) {
unsigned int val = mount->dumpfile_time_limit;
__read_unsigned_int(configuration, doc, node, &val, 0, UINT_MAX);
mount->dumpfile_time_limit = val;
} else if (xmlStrcmp(node->name, XMLSTR("intro")) == 0) {
mount->intro_filename = (char *)xmlNodeListGetString(doc,
node->xmlChildrenNode, 1);
@ -2989,6 +2997,10 @@ static void merge_mounts(mount_proxy * dst, mount_proxy * src)
if (!dst->dumpfile)
dst->dumpfile = (char*)xmlStrdup((xmlChar*)src->dumpfile);
if (!dst->dumpfile_size_limit)
dst->dumpfile_size_limit = src->dumpfile_size_limit;
if (!dst->dumpfile_time_limit)
dst->dumpfile_time_limit = src->dumpfile_time_limit;
if (!dst->intro_filename)
dst->intro_filename = (char*)xmlStrdup((xmlChar*)src->intro_filename);
if (!dst->fallback_when_full)

View File

@ -88,6 +88,8 @@ typedef struct _mount_proxy {
* NULL to not dump.
*/
char *dumpfile;
uint64_t dumpfile_size_limit;
unsigned int dumpfile_time_limit;
/* Send contents of file to client before the stream */
char *intro_filename;
/* Switch new listener to fallback source when max listeners reached */

View File

@ -368,33 +368,16 @@ static void ebml_free_client_data (client_t *client)
client->format_data = NULL;
}
static void ebml_write_buf_to_file_fail (source_t *source)
{
ICECAST_LOG_WARN("Write to dump file failed, disabling");
fclose (source->dumpfile);
source->dumpfile = NULL;
}
static void ebml_write_buf_to_file (source_t *source, refbuf_t *refbuf)
{
ebml_source_state_t *ebml_source_state = source->format->_state;
if ( ! ebml_source_state->file_headers_written)
{
if (fwrite (ebml_source_state->header->data, 1,
ebml_source_state->header->len,
source->dumpfile) != ebml_source_state->header->len)
ebml_write_buf_to_file_fail(source);
else
if (!ebml_source_state->file_headers_written) {
if (source_write_dumpfile(source, ebml_source_state->header->data, ebml_source_state->header->len))
ebml_source_state->file_headers_written = true;
}
if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len)
{
ebml_write_buf_to_file_fail(source);
}
source_write_dumpfile(source, refbuf->data, refbuf->len);
}
/* internal ebml parsing */

View File

@ -760,13 +760,6 @@ static void free_mp3_client_data (client_t *client)
static void write_mp3_to_file (source_t *source, refbuf_t *refbuf)
{
if (refbuf->len == 0)
return;
if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) < (size_t)refbuf->len)
{
ICECAST_LOG_WARN("Write to dump file failed, disabling");
fclose (source->dumpfile);
source->dumpfile = NULL;
}
source_write_dumpfile(source, refbuf->data, refbuf->len);
}

View File

@ -565,21 +565,6 @@ static int write_buf_to_client(client_t *client)
}
static int write_ogg_data (source_t *source, refbuf_t *refbuf)
{
int ret = 1;
if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len)
{
ICECAST_LOG_WARN("Write to dump file failed, disabling");
fclose (source->dumpfile);
source->dumpfile = NULL;
ret = 0;
}
return ret;
}
static void write_ogg_to_file (source_t *source, refbuf_t *refbuf)
{
ogg_state_t *ogg_info = source->format->_state;
@ -589,13 +574,11 @@ static void write_ogg_to_file (source_t *source, refbuf_t *refbuf)
refbuf_t *header = refbuf->associated;
while (header)
{
if (write_ogg_data (source, header) == 0)
if (!source_write_dumpfile(source, header->data, header->len))
return;
header = header->next;
}
ogg_info->file_headers = refbuf->associated;
}
write_ogg_data (source, refbuf);
source_write_dumpfile(source, refbuf->data, refbuf->len);
}

View File

@ -54,6 +54,10 @@ static size_t skipchar(char *text, char skip, size_t len)
return ret;
}
static void text_to_file (source_t *source, refbuf_t *refbuf)
{
source_write_dumpfile(source, refbuf->data, refbuf->len);
}
static refbuf_t *text_get_buffer(source_t *source)
{
@ -97,7 +101,7 @@ int format_text_get_plugin(source_t *source)
plugin->write_buf_to_client = format_generic_write_to_client;
plugin->create_client_data = NULL;
plugin->free_plugin = text_free_plugin;
plugin->write_buf_to_file = NULL;
plugin->write_buf_to_file = text_to_file;
plugin->set_tag = NULL;
plugin->apply_settings = NULL;

View File

@ -506,6 +506,10 @@ static refbuf_t *get_next_buffer (source_t *source)
"%"PRIu64, source->format->read_bytes);
stats_event_args (source->mount, "total_bytes_sent",
"%"PRIu64, source->format->sent_bytes);
if (source->dumpfile) {
stats_event_args(source->mount, "dumpfile_written",
"%"PRIu64, source->dumpfile_written);
}
source->client_stats_update = current + 5;
}
if (fds < 0)
@ -607,24 +611,31 @@ static void send_to_listener (source_t *source, client_t *client, int deletion_e
/* Open the file for stream dumping.
* This function should do all processing of the filename.
*/
static FILE * source_open_dumpfile(const char * filename) {
static void source_open_dumpfile(source_t *source) {
const char *filename = source->dumpfilename;
#ifndef _WIN32
/* some of the below functions seems not to be standard winapi functions */
time_t curtime = time(NULL);
char buffer[PATH_MAX];
time_t curtime;
struct tm *loctime;
/* Get the current time. */
curtime = time (NULL);
/* Convert it to local time representation. */
loctime = localtime (&curtime);
loctime = localtime(&curtime);
strftime (buffer, sizeof(buffer), filename, loctime);
strftime(buffer, sizeof(buffer), filename, loctime);
filename = buffer;
#endif
return fopen (filename, "ab");
source->dumpfile = fopen(filename, "ab");
if (source->dumpfile) {
source->dumpfile_start = curtime;
stats_event(source->mount, "dumpfile_written", "0");
stats_event_time_iso8601(source->mount, "dumpfile_start");
} else {
ICECAST_LOG_WARN("Cannot open dump file \"%s\" for appending: %s, disabling.",
source->dumpfilename, strerror(errno));
}
}
/* Perform any initialisation just before the stream data is processed, the header
@ -647,14 +658,7 @@ static void source_init (source_t *source)
stats_event (source->mount, "listenurl", listenurl);
if (source->dumpfilename != NULL)
{
source->dumpfile = source_open_dumpfile (source->dumpfilename);
if (source->dumpfile == NULL)
{
ICECAST_LOG_WARN("Cannot open dump file \"%s\" for appending: %s, disabling.",
source->dumpfilename, strerror(errno));
}
}
source_open_dumpfile(source);
/* grab a read lock, to make sure we get a chance to cleanup */
thread_rwlock_rlock (source->shutdown_rwlock);
@ -1179,13 +1183,27 @@ static void source_apply_mount (ice_config_t *config, source_t *source, mount_pr
source->fallback_mount = NULL;
}
/* Dumpfile settings */
if (mountinfo && mountinfo->dumpfile) {
util_replace_string(&(source->dumpfilename), mountinfo->dumpfile);
} else {
free(source->dumpfilename);
source->dumpfilename = NULL;
if (source->dumpfile) {
ICECAST_LOG_INFO("Stopping dumpfile as it is now de-configured for source %p at mountpoint %#H.", source, source->mount);
source_kill_dumpfile(source);
}
}
if (mountinfo) {
source->dumpfile_size_limit = mountinfo->dumpfile_size_limit;
source->dumpfile_time_limit = mountinfo->dumpfile_time_limit;
} else {
source->dumpfile_size_limit = 0;
source->dumpfile_time_limit = 0;
}
if (source->intro_file)
{
fclose (source->intro_file);
@ -1461,3 +1479,46 @@ void source_recheck_mounts (int update_all)
avl_tree_unlock (global.source_tree);
config_release_config();
}
/* Writes a buffer of raw data to a dumpfile. returns true if the write was successful (and complete). */
bool source_write_dumpfile(source_t *source, const void *buffer, size_t len)
{
if (!source->dumpfile)
return false;
if (!len)
return true;
if (fwrite(buffer, 1, len, source->dumpfile) != len) {
ICECAST_LOG_WARN("Write to dump file failed, disabling");
source_kill_dumpfile(source);
return false;
}
source->dumpfile_written += len;
if (source->dumpfile_size_limit && source->dumpfile_written > source->dumpfile_size_limit) {
ICECAST_LOG_INFO("Dumpfile for source %p at mountpoint %#H reached size limit. Dumpfile will be disabled.", source, source->mount);
source_kill_dumpfile(source);
} else if (source->dumpfile_time_limit) {
time_t now = time(NULL);
if (now > (source->dumpfile_start + source->dumpfile_time_limit)) {
ICECAST_LOG_INFO("Dumpfile for source %p at mountpoint %#H reached time limit. Dumpfile will be disabled.", source, source->mount);
source_kill_dumpfile(source);
}
}
return true;
}
void source_kill_dumpfile(source_t *source)
{
if (!source->dumpfile)
return;
fclose(source->dumpfile);
source->dumpfile = NULL;
source->dumpfile_written = 0;
stats_event(source->mount, "dumpfile_written", NULL);
stats_event(source->mount, "dumpfile_start", NULL);
}

View File

@ -54,8 +54,15 @@ struct source_tag {
FILE *intro_file;
/* Dumpfile related data */
/* Config */
char *dumpfilename; /* Name of a file to dump incoming stream to */
uint64_t dumpfile_size_limit;
unsigned int dumpfile_time_limit;
/* Runtime */
FILE *dumpfile;
time_t dumpfile_start;
uint64_t dumpfile_written;
unsigned long peak_listeners;
unsigned long listeners;
@ -104,6 +111,10 @@ int source_remove_client(void *key);
void source_main(source_t *source);
void source_recheck_mounts (int update_all);
/* Writes a buffer of raw data to a dumpfile. returns true if the write was successful (and complete). */
bool source_write_dumpfile(source_t *source, const void *buffer, size_t len);
void source_kill_dumpfile(source_t *source);
extern mutex_t move_clients_mutex;
#endif

View File

@ -156,6 +156,10 @@ ul.boxnav > li.critical > a, a.critical, input[type='submit'].critical {
background-color: #ff704d !important;
}
ul.boxnav > li.disabled > a, a.disabled, input[type='submit'].disabled {
background-color: #a4a4a4 !important;
}
th.actions {
width: 10%;
}