diff --git a/recording-daemon/notify.c b/recording-daemon/notify.c index 0b7396a20..b3ae3ace3 100644 --- a/recording-daemon/notify.c +++ b/recording-daemon/notify.c @@ -143,6 +143,11 @@ cleanup: return success; } +static void failed_http(notif_req_t *req) { + if (req->content) + output_content_failure(req->content); +} + static bool do_notify_command(notif_req_t *req) { ilog(LOG_DEBUG, "Executing notification command for '%s%s%s'", FMT_M(req->name)); @@ -190,6 +195,9 @@ static void do_notify(void *p, void *u) { "Giving up", FMT_M(req->name), req->retries); + + if (req->action->failed) + req->action->failed(req); } req->action->cleanup(req); @@ -335,6 +343,7 @@ static const notif_action_t http_action = { .setup = notify_req_setup_http, .perform = do_notify_http, .cleanup = cleanup_http, + .failed = failed_http, }; diff --git a/recording-daemon/output.c b/recording-daemon/output.c index 752b83f63..697e4f691 100644 --- a/recording-daemon/output.c +++ b/recording-daemon/output.c @@ -522,12 +522,15 @@ static bool output_config(sink_t *sink, output_t *output, const format_t *reques static void content_free(content_t *s) { g_string_free(s->s, TRUE); + g_free(s->name); } -static content_t *output_make_content(GString *s) { +static content_t *output_make_content(GString *s, output_t *output) { content_t *ret = obj_alloc0(content_t, content_free); ret->s = s; + if (output->file_name && output->file_name[0]) + ret->name = g_strdup(output->file_name); return ret; } @@ -557,12 +560,63 @@ content_t *output_get_content(output_t *output) { return NULL; } - output->content = output_make_content(content); + output->content = output_make_content(content, output); return obj_get(output->content); } +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FILE, fclose) + + +void output_content_failure(content_t *c) { + unsigned int exp = 0; + if (!atomic_compare_exchange(&c->failed, &exp, 1)) + return; // already done + + // find output file name + const char *prefix; + char buf[33]; + if (c->name) + prefix = c->name; + else { + rand_hex_str(buf, 16); + prefix = buf; + } + + g_autoptr(char) fn = g_strdup_printf("%s/backup-%s", output_dir, prefix); + + if (g_file_test(fn, G_FILE_TEST_EXISTS)) { + char suffix[17]; + rand_hex_str(suffix, 8); + g_free(fn); + fn = g_strdup_printf("%s/backup-%s%s", output_dir, prefix, suffix); + if (g_file_test(fn, G_FILE_TEST_EXISTS)) { + ilog(LOG_ERR, "Failed to write emergency backup to '%s': file exists", + fn); + return; + } + } + + g_autoptr(FILE) fp = fopen(fn, "wb"); + if (!fp) { + ilog(LOG_ERR, "Failed to write emergency backup to '%s': %s", + fn, strerror(errno)); + return; + } + + ssize_t written = fwrite(c->s->str, 1, c->s->len, fp); + if (written < 0) + ilog(LOG_ERR, "Failed to write emergency backup to '%s': %s", + fn, strerror(errno)); + else if (written != c->s->len) + ilog(LOG_ERR, "Failed to write emergency backup to '%s': short write", + fn); + else + ilog(LOG_NOTICE, "Wrote emergency backup to '%s'", fn); +} + + static bool output_shutdown(output_t *output) { if (!output) return false; @@ -581,7 +635,7 @@ static bool output_shutdown(output_t *output) { else if (output->membuf) { if (output->membuf->len) { obj_release(output->content); - output->content = output_make_content(output->membuf); + output->content = output_make_content(output->membuf, output); output->membuf = NULL; ret = true; } diff --git a/recording-daemon/output.h b/recording-daemon/output.h index 67eaa87c4..ff3bc51ee 100644 --- a/recording-daemon/output.h +++ b/recording-daemon/output.h @@ -13,6 +13,7 @@ void output_init(const char *format); output_t *output_new_ext(metafile_t *, const char *type, const char *kind, const char *label); void output_close(metafile_t *, output_t *, tag_t *, bool discard); content_t *output_get_content(output_t *); +void output_content_failure(content_t *); void sink_init(sink_t *); diff --git a/recording-daemon/types.h b/recording-daemon/types.h index 30b887864..5c86dbf05 100644 --- a/recording-daemon/types.h +++ b/recording-daemon/types.h @@ -228,12 +228,16 @@ struct decode_s { struct content_s { struct obj obj; GString *s; + + char *name; + unsigned int failed; }; struct notif_action_s { const char *name; void (*setup)(notif_req_t *, output_t *o, metafile_t *mf, tag_t *tag); bool (*perform)(notif_req_t *); + void (*failed)(notif_req_t *); void (*cleanup)(notif_req_t *); };