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:
commit
b49602dbce
@ -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
|
||||
|
@ -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}&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>
|
||||
|
24
admin/ui/confirmkilldumpfile.xsl
Normal file
24
admin/ui/confirmkilldumpfile.xsl
Normal 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>
|
||||
|
21
src/admin.c
21
src/admin.c
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
93
src/source.c
93
src/source.c
@ -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);
|
||||
}
|
||||
|
11
src/source.h
11
src/source.h
@ -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
|
||||
|
@ -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%;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user