diff --git a/admin/Makefile.am b/admin/Makefile.am
index 320854ba..eb2ee624 100644
--- a/admin/Makefile.am
+++ b/admin/Makefile.am
@@ -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
diff --git a/admin/includes/mountnav.xsl b/admin/includes/mountnav.xsl
index 44814cb9..1f002138 100644
--- a/admin/includes/mountnav.xsl
+++ b/admin/includes/mountnav.xsl
@@ -8,6 +8,14 @@
Move listeners
Metadata
Set fallback
+
+
+ Stop dumpfile
+
+
+ No dumpfile running
+
+
Kill source
diff --git a/admin/ui/confirmkilldumpfile.xsl b/admin/ui/confirmkilldumpfile.xsl
new file mode 100644
index 00000000..8654411b
--- /dev/null
+++ b/admin/ui/confirmkilldumpfile.xsl
@@ -0,0 +1,24 @@
+
+
+
+ Confirm Stopping dumpfile
+
+
+
+
+
+ Please confirm stopping the dumpfile for
.
+ /admin/streamlist.xsl
+ /admin/dumpfilecontrol.xsl
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/admin.c b/src/admin.c
index 515b3115..98187ca9 100644
--- a/src/admin.c
+++ b/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)
diff --git a/src/cfgfile.c b/src/cfgfile.c
index bd286448..b6f036b7 100644
--- a/src/cfgfile.c
+++ b/src/cfgfile.c
@@ -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)
diff --git a/src/cfgfile.h b/src/cfgfile.h
index 988d5a41..7539843e 100644
--- a/src/cfgfile.h
+++ b/src/cfgfile.h
@@ -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 */
diff --git a/src/format_ebml.c b/src/format_ebml.c
index 1e2353fa..b7954b87 100644
--- a/src/format_ebml.c
+++ b/src/format_ebml.c
@@ -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 */
diff --git a/src/format_mp3.c b/src/format_mp3.c
index bbaaee7a..11ba7950 100644
--- a/src/format_mp3.c
+++ b/src/format_mp3.c
@@ -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);
}
diff --git a/src/format_ogg.c b/src/format_ogg.c
index b0d49d52..afc80d7d 100644
--- a/src/format_ogg.c
+++ b/src/format_ogg.c
@@ -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);
}
-
-
diff --git a/src/format_text.c b/src/format_text.c
index b9261b35..5232da00 100644
--- a/src/format_text.c
+++ b/src/format_text.c
@@ -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;
diff --git a/src/source.c b/src/source.c
index 327ba167..2926723b 100644
--- a/src/source.c
+++ b/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);
+}
diff --git a/src/source.h b/src/source.h
index 784dcaf6..a9b6c042 100644
--- a/src/source.h
+++ b/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
diff --git a/web/assets/css/style.css b/web/assets/css/style.css
index 0240029d..eec468b0 100644
--- a/web/assets/css/style.css
+++ b/web/assets/css/style.css
@@ -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%;
}