From eea05c878fcf15a395c55c801bdfe9ca9e143996 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 31 Mar 2023 14:20:30 -0400 Subject: [PATCH] MT#56759 support discarding call recordings When the `discard-recording` flag is given in one of the commands to rtpengine (e.g. in the `delete` command), the metafile is renamed to a .DISCARD suffix and then deleted. The recording daemon then, seeing the .DISCARD suffix, proceeds to immediately close all recordings, delete the files if any, and delete the entries from the DB. Change-Id: I3f0cac129f2d56cbccd770d43bf434dea6c0a0db --- daemon/call.c | 2 +- daemon/call_interfaces.c | 30 ++++++++++++--- daemon/recording.c | 74 ++++++++++++++++++++++++++++++------- docs/ng_control_protocol.md | 5 +++ include/call_interfaces.h | 1 + include/recording.h | 5 ++- recording-daemon/metafile.c | 24 +++++++++--- recording-daemon/output.c | 19 +++++++--- recording-daemon/output.h | 2 +- recording-daemon/packet.c | 2 +- recording-daemon/types.h | 1 + 11 files changed, 130 insertions(+), 35 deletions(-) diff --git a/daemon/call.c b/daemon/call.c index f5a4afa88..060f6a52d 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -3637,7 +3637,7 @@ static void __call_cleanup(struct call *c) { obj_put(sfd); } - recording_finish(c); + recording_finish(c, false); } /* called lock-free, but must hold a reference to the call */ diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index aa02b4a76..991f3ce65 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -1031,6 +1031,9 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { case CSH_LOOKUP("record-call"): out->record_call = 1; break; + case CSH_LOOKUP("discard-recording"): + out->discard_recording = 1; + break; case CSH_LOOKUP("inactive"): out->inactive = 1; break; @@ -2119,6 +2122,7 @@ const char *call_delete_ng(bencode_item_t *input, bencode_item_t *output) { str fromtag, totag, viabranch, callid; bencode_item_t *flags, *it; bool fatal = false; + bool discard = false; int delete_delay; if (!bencode_dictionary_get_str(input, "call-id", &callid)) @@ -2132,6 +2136,8 @@ const char *call_delete_ng(bencode_item_t *input, bencode_item_t *output) { for (it = flags->child; it; it = it->sibling) { if (!bencode_strcmp(it, "fatal")) fatal = true; + else if (!bencode_strcmp(it, "discard-recording")) + discard = true; } } delete_delay = bencode_dictionary_get_int_str(input, "delete-delay", -1); @@ -2146,13 +2152,23 @@ const char *call_delete_ng(bencode_item_t *input, bencode_item_t *output) { } } - if (call_delete_branch_by_id(&callid, &viabranch, &fromtag, &totag, output, delete_delay)) { - if (fatal) - return "Call-ID not found or tags didn't match"; - bencode_dictionary_add_string(output, "warning", "Call-ID not found or tags didn't match"); - } + struct call *c = call_get(&callid); + if (!c) + goto err; + + if (discard) + recording_discard(c); + + if (call_delete_branch(c, &viabranch, &fromtag, &totag, output, delete_delay)) + goto err; return NULL; + +err: + if (fatal) + return "Call-ID not found or tags didn't match"; + bencode_dictionary_add_string(output, "warning", "Call-ID not found or tags didn't match"); + return NULL; } static void ng_stats(bencode_item_t *d, const struct stream_stats *s, struct stream_stats *totals) { @@ -2593,6 +2609,10 @@ static void stop_recording_fn(bencode_item_t *input, struct call *call) { pause_recording_fn(input, call); return; } + if (bencode_strcmp(child, "discard-recording") == 0) { + recording_discard(call); + return; + } } } diff --git a/daemon/recording.c b/daemon/recording.c index 6c385934b..253655a80 100644 --- a/daemon/recording.c +++ b/daemon/recording.c @@ -46,14 +46,14 @@ static void init_all(struct call *call); static void sdp_after_all(struct recording *recording, GString *str, struct call_monologue *ml, enum call_opmode opmode); static void dump_packet_all(struct media_packet *mp, const str *s); -static void finish_all(struct call *call); +static void finish_all(struct call *call, bool discard); // pcap methods static int rec_pcap_create_spool_dir(const char *dirpath); static void rec_pcap_init(struct call *); static void sdp_after_pcap(struct recording *, GString *str, struct call_monologue *, enum call_opmode opmode); static void dump_packet_pcap(struct media_packet *mp, const str *s); -static void finish_pcap(struct call *); +static void finish_pcap(struct call *, bool discard); static void response_pcap(struct recording *, bencode_item_t *); // proc methods @@ -62,7 +62,7 @@ static void sdp_before_proc(struct recording *, const str *, struct call_monolog static void sdp_after_proc(struct recording *, GString *str, struct call_monologue *, enum call_opmode opmode); static void meta_chunk_proc(struct recording *, const char *, const str *); static void update_flags_proc(struct call *call, bool streams); -static void finish_proc(struct call *); +static void finish_proc(struct call *, bool discard); static void dump_packet_proc(struct media_packet *mp, const str *s); static void init_stream_proc(struct packet_stream *); static void setup_stream_proc(struct packet_stream *); @@ -393,12 +393,20 @@ void recording_stop(struct call *call) { } ilog(LOG_NOTICE, "Turning off call recording."); - recording_finish(call); + recording_finish(call, false); } void recording_pause(struct call *call) { ilog(LOG_NOTICE, "Pausing call recording."); recording_update_flags(call, true); } +void recording_discard(struct call *call) { + call->recording_on = 0; + if (!call->recording) + return; + ilog(LOG_NOTICE, "Turning off call recording and discarding outputs."); + recording_finish(call, true); +} + /** * @@ -424,6 +432,10 @@ void detect_setup_recording(struct call *call, const struct sdp_ng_flags *flags) call->recording_on = 0; recording_stop(call); } + else if (!str_cmp(recordcall, "discard") || flags->discard_recording) { + call->recording_on = 0; + recording_discard(call); + } else if (recordcall->len != 0) ilog(LOG_INFO, "\"record-call\" flag "STR_FORMAT" is invalid flag.", STR_FMT(recordcall)); } @@ -572,7 +584,23 @@ static void rec_pcap_meta_finish_file(struct call *call) { mutex_destroy(&recording->u.pcap.recording_lock); g_clear_pointer(&recording->u.pcap.meta_filepath, g_free); +} + +/** + * Closes and discards all output files. + */ +static void rec_pcap_meta_discard_file(struct call *call) { + struct recording *recording = call->recording; + if (recording == NULL || recording->u.pcap.meta_fp == NULL) + return; + + fclose(recording->u.pcap.meta_fp); + recording->u.pcap.meta_fp = NULL; + + unlink(recording->u.pcap.recording_path); + unlink(recording->u.pcap.meta_filepath); + g_clear_pointer(&recording->u.pcap.meta_filepath, free); } /** @@ -673,9 +701,12 @@ static void dump_packet_pcap(struct media_packet *mp, const str *s) { mutex_unlock(&recording->u.pcap.recording_lock); } -static void finish_pcap(struct call *call) { +static void finish_pcap(struct call *call, bool discard) { rec_pcap_recording_finish_file(call->recording); - rec_pcap_meta_finish_file(call); + if (!discard) + rec_pcap_meta_finish_file(call); + else + rec_pcap_meta_discard_file(call); } static void response_pcap(struct recording *recording, bencode_item_t *output) { @@ -692,7 +723,7 @@ static void response_pcap(struct recording *recording, bencode_item_t *output) { -void recording_finish(struct call *call) { +void recording_finish(struct call *call, bool discard) { if (!call || !call->recording) return; @@ -700,7 +731,7 @@ void recording_finish(struct call *call) { struct recording *recording = call->recording; - _rm(finish, call); + _rm(finish, call, discard); g_clear_pointer(&recording->meta_prefix, g_free); g_clear_pointer(&recording->escaped_callid, free); @@ -810,7 +841,7 @@ static void sdp_after_proc(struct recording *recording, GString *str, struct cal "SDP from %u after %s", ml->unique_id, get_opmode_text(opmode)); } -static void finish_proc(struct call *call) { +static void finish_proc(struct call *call, bool discard) { struct recording *recording = call->recording; if (!kernel.is_open) return; @@ -822,10 +853,25 @@ static void finish_proc(struct call *call) { struct packet_stream *ps = l->data; ps->recording.u.proc.stream_idx = UNINIT_IDX; } - int ret = unlink(recording->u.proc.meta_filepath); + + const char *unlink_fn = recording->u.proc.meta_filepath; + AUTO_CLEANUP_GBUF(discard_fn); + if (discard) { + discard_fn = g_strdup_printf("%s.DISCARD", recording->u.proc.meta_filepath); + int ret = rename(recording->u.proc.meta_filepath, discard_fn); + if (ret) + ilog(LOG_ERR, "Failed to rename metadata file \"%s\" to \"%s\": %s", + recording->u.proc.meta_filepath, + discard_fn, + strerror(errno)); + unlink_fn = discard_fn; + } + + int ret = unlink(unlink_fn); if (ret) ilog(LOG_ERR, "Failed to delete metadata file \"%s\": %s", - recording->u.proc.meta_filepath, strerror(errno)); + unlink_fn, strerror(errno)); + g_clear_pointer(&recording->u.proc.meta_filepath, free); } @@ -969,7 +1015,7 @@ static void dump_packet_all(struct media_packet *mp, const str *s) { dump_packet_proc(mp, s); } -static void finish_all(struct call *call) { - finish_pcap(call); - finish_proc(call); +static void finish_all(struct call *call, bool discard) { + finish_pcap(call, discard); + finish_proc(call, discard); } diff --git a/docs/ng_control_protocol.md b/docs/ng_control_protocol.md index 628f7b864..f1888fc5c 100644 --- a/docs/ng_control_protocol.md +++ b/docs/ng_control_protocol.md @@ -813,6 +813,11 @@ Spaces in each string may be replaced by hyphens. the DSP to detect in-band DTMF audio tones even when it wouldn't otherwise be necessary. +* `discard recording` + + When file recording is in use, instructs the recording daemon to discard + (delete) the recording files, as well as the database entries if present. + * `early media` Used in conjunction with the audio player. If set, audio playback is diff --git a/include/call_interfaces.h b/include/call_interfaces.h index aa4558f8c..4869d0a96 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -157,6 +157,7 @@ struct sdp_ng_flags { siprec:1, fragment:1, record_call:1, + discard_recording:1, debug:1, inactive:1, loop_protect:1, diff --git a/include/recording.h b/include/recording.h index 249da0be1..2a8e7348a 100644 --- a/include/recording.h +++ b/include/recording.h @@ -76,7 +76,7 @@ struct recording_method { void (*update_flags)(struct call *call, bool streams); void (*dump_packet)(struct media_packet *, const str *s); - void (*finish)(struct call *); + void (*finish)(struct call *, bool discard); void (*response)(struct recording *, bencode_item_t *); void (*init_stream_struct)(struct packet_stream *); @@ -121,12 +121,13 @@ void update_metadata_monologue(struct call_monologue *ml, str *metadata); void recording_start(struct call *call, const char *prefix, str *output_dest); void recording_pause(struct call *call); void recording_stop(struct call *call); +void recording_discard(struct call *call); #define meta_write_sdp_before(args...) _rm(sdp_before, args) #define meta_write_sdp_after(args...) _rm(sdp_after, args) -void recording_finish(struct call *); +void recording_finish(struct call *, bool discard); diff --git a/recording-daemon/metafile.c b/recording-daemon/metafile.c index c67deba58..163f03b0f 100644 --- a/recording-daemon/metafile.c +++ b/recording-daemon/metafile.c @@ -26,7 +26,7 @@ static void meta_free(void *ptr) { metafile_t *mf = ptr; dbg("freeing metafile info for %s%s%s", FMT_M(mf->name)); - output_close(mf, mf->mix_out, NULL); + output_close(mf, mf->mix_out, NULL, mf->discard); mix_destroy(mf->mix); db_close_call(mf); g_string_chunk_free(mf->gsc); @@ -363,16 +363,28 @@ void metafile_delete(char *name) { pthread_mutex_lock(&metafiles_lock); metafile_t *mf = g_hash_table_lookup(metafiles, name); if (!mf) { - // nothing to do - pthread_mutex_unlock(&metafiles_lock); - return; + // has it been renamed? + size_t len = strlen(name); + char *suffix = name + len - strlen(".DISCARD"); + if (suffix > name && strcmp(suffix, ".DISCARD") == 0) { + *suffix = '\0'; + mf = g_hash_table_lookup(metafiles, name); + if (mf) + mf->discard = 1; + *suffix = '.'; + } + if (!mf) { + // nothing to do + pthread_mutex_unlock(&metafiles_lock); + return; + } } // switch locks and remove entry pthread_mutex_lock(&mf->lock); - g_hash_table_remove(metafiles, name); + g_hash_table_remove(metafiles, mf->name); pthread_mutex_unlock(&metafiles_lock); - ilog(LOG_INFO, "Recording for call '%s%s%s' finished", FMT_M(name)); + ilog(LOG_INFO, "Recording for call '%s%s%s' finished", FMT_M(mf->name)); meta_destroy(mf); diff --git a/recording-daemon/output.c b/recording-daemon/output.c index a747e9360..4fa115375 100644 --- a/recording-daemon/output.c +++ b/recording-daemon/output.c @@ -339,15 +339,24 @@ static bool output_shutdown(output_t *output) { } -void output_close(metafile_t *mf, output_t *output, tag_t *tag) { +void output_close(metafile_t *mf, output_t *output, tag_t *tag, bool discard) { if (!output) return; - if (output_shutdown(output)) { - db_close_stream(output); - notify_push_output(output, mf, tag); + if (!discard) { + if (output_shutdown(output)) { + db_close_stream(output); + notify_push_output(output, mf, tag); + } + else + db_delete_stream(mf, output); } - else + else { + output_shutdown(output); + if (unlink(output->filename)) + ilog(LOG_WARN, "Failed to unlink '%s%s%s': %s", + FMT_M(output->filename), strerror(errno)); db_delete_stream(mf, output); + } encoder_free(output->encoder); g_clear_pointer(&output->full_filename, g_free); g_clear_pointer(&output->file_path, g_free); diff --git a/recording-daemon/output.h b/recording-daemon/output.h index f4264b23a..c4af6cf79 100644 --- a/recording-daemon/output.h +++ b/recording-daemon/output.h @@ -12,7 +12,7 @@ void output_init(const char *format); output_t *output_new(const char *path, const char *call, const char *type, const char *kind, const char *label); output_t *output_new_from_full_path(const char *path, char *name, const char *kind); -void output_close(metafile_t *, output_t *, tag_t *); +void output_close(metafile_t *, output_t *, tag_t *, bool discard); int output_config(output_t *output, const format_t *requested_format, format_t *actual_format); int output_add(output_t *output, AVFrame *frame); diff --git a/recording-daemon/packet.c b/recording-daemon/packet.c index 3220482c5..06b75cdb9 100644 --- a/recording-daemon/packet.c +++ b/recording-daemon/packet.c @@ -145,7 +145,7 @@ void ssrc_tls_state(ssrc_t *ssrc) { void ssrc_close(ssrc_t *s) { - output_close(s->metafile, s->output, tag_get(s->metafile, s->stream->tag)); + output_close(s->metafile, s->output, tag_get(s->metafile, s->stream->tag), s->metafile->discard); s->output = NULL; for (int i = 0; i < G_N_ELEMENTS(s->decoders); i++) { decoder_free(s->decoders[i]); diff --git a/recording-daemon/types.h b/recording-daemon/types.h index e4b7ef7c2..4d699fcf8 100644 --- a/recording-daemon/types.h +++ b/recording-daemon/types.h @@ -141,6 +141,7 @@ struct metafile_s { unsigned int recording_on:1; unsigned int forwarding_on:1; + unsigned int discard:1; };