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; };