diff --git a/src/lib_ccx/ccx_decoders_common.c b/src/lib_ccx/ccx_decoders_common.c index 471b42819..ca323c371 100644 --- a/src/lib_ccx/ccx_decoders_common.c +++ b/src/lib_ccx/ccx_decoders_common.c @@ -231,6 +231,31 @@ void dinit_cc_decode(struct lib_cc_decode **ctx) dinit_timing_ctx(&lctx->timing); free_decoder_context(lctx->prev); free_subtitle(lctx->dec_sub.prev); + /* Free the embedded dec_sub's data field (allocated by write_cc_buffer) */ + if (lctx->dec_sub.datatype == CC_DATATYPE_DVB) + { + struct cc_bitmap *bitmap = (struct cc_bitmap *)lctx->dec_sub.data; + if (bitmap) + { + freep(&bitmap->data0); + freep(&bitmap->data1); + } + } + /* Free any leftover XDS strings that weren't processed by the encoder */ + if (lctx->dec_sub.type == CC_608 && lctx->dec_sub.data) + { + struct eia608_screen *data = (struct eia608_screen *)lctx->dec_sub.data; + for (int i = 0; i < lctx->dec_sub.nb_data; i++, data++) + { + if (data->format == SFORMAT_XDS && data->xds_str) + { + freep(&data->xds_str); + } + } + } + freep(&lctx->dec_sub.data); + /* Note: xds_ctx is freed in general_loop.c, mp4.c etc. during normal processing. + Don't free it here as it may cause double-free if already freed elsewhere. */ freep(ctx); } diff --git a/src/lib_ccx/ccx_decoders_xds.c b/src/lib_ccx/ccx_decoders_xds.c index dfd94e211..159700cb6 100644 --- a/src/lib_ccx/ccx_decoders_xds.c +++ b/src/lib_ccx/ccx_decoders_xds.c @@ -175,6 +175,8 @@ void xdsprint(struct cc_subtitle *sub, struct ccx_decoders_xds_context *ctx, con if (n > -1 && n < size) { write_xds_string(sub, ctx, p, n); + /* Note: Don't free(p) here - the pointer is stored in data->xds_str + and will be freed by the encoder or decoder cleanup code */ return; } /* Else try again with more space. */ diff --git a/src/lib_ccx/ccx_demuxer.c b/src/lib_ccx/ccx_demuxer.c index 3469d03fa..9a0993760 100644 --- a/src/lib_ccx/ccx_demuxer.c +++ b/src/lib_ccx/ccx_demuxer.c @@ -326,7 +326,7 @@ void ccx_demuxer_delete(struct ccx_demuxer **ctx) struct ccx_demuxer *init_demuxer(void *parent, struct demuxer_cfg *cfg) { int i; - struct ccx_demuxer *ctx = malloc(sizeof(struct ccx_demuxer)); + struct ccx_demuxer *ctx = calloc(1, sizeof(struct ccx_demuxer)); if (!ctx) return NULL; diff --git a/src/lib_ccx/ccx_encoders_common.c b/src/lib_ccx/ccx_encoders_common.c index ac2af699a..135477531 100644 --- a/src/lib_ccx/ccx_encoders_common.c +++ b/src/lib_ccx/ccx_encoders_common.c @@ -924,6 +924,11 @@ int encode_sub(struct encoder_ctx *context, struct cc_subtitle *sub) // After adding delay, if start/end time is lower than 0, then continue with the next subtitle if (data->start_time < 0 || data->end_time <= 0) { + // Free XDS string if skipping to avoid memory leak + if (data->format == SFORMAT_XDS && data->xds_str) + { + freep(&data->xds_str); + } continue; } diff --git a/src/lib_ccx/ccx_encoders_spupng.c b/src/lib_ccx/ccx_encoders_spupng.c index 1bfe3f248..09fca63f5 100644 --- a/src/lib_ccx/ccx_encoders_spupng.c +++ b/src/lib_ccx/ccx_encoders_spupng.c @@ -570,10 +570,8 @@ int write_image(struct pixel_t *buffer, FILE *fp, int width, int height) png_write_end(png_ptr, NULL); finalise: - if (info_ptr != NULL) - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); if (png_ptr != NULL) - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + png_destroy_write_struct(&png_ptr, &info_ptr); if (row != NULL) free(row); diff --git a/src/lib_ccx/general_loop.c b/src/lib_ccx/general_loop.c index 3ec78e0a4..47f63fc31 100644 --- a/src/lib_ccx/general_loop.c +++ b/src/lib_ccx/general_loop.c @@ -669,24 +669,33 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str { // telxcc_update_gt(dec_ctx->private_data, ctx->demux_ctx->global_timestamp); - /* Process Teletext packets even when no encoder context exists (e.g. -out=report). - This enables tlt_process_pes_packet() to detect subtitle pages by populating - the seen_sub_page[] array inside the teletext decoder. */ - int sentence_cap = enc_ctx ? enc_ctx->sentence_cap : 0; - - ret = tlt_process_pes_packet( - dec_ctx, - data_node->buffer, - data_node->len, - dec_sub, - sentence_cap); - - /* If Teletext decoding fails with invalid data, abort processing */ - if (ret == CCX_EINVAL) - return ret; - - /* Mark processed byte count */ - got = data_node->len; + /* Check if teletext context is still valid (may have been freed by dinit_cap + during PAT change while stream was being processed) */ + if (!dec_ctx->private_data) + { + got = data_node->len; // Skip processing, context was freed + } + else + { + /* Process Teletext packets even when no encoder context exists (e.g. -out=report). + This enables tlt_process_pes_packet() to detect subtitle pages by populating + the seen_sub_page[] array inside the teletext decoder. */ + int sentence_cap = enc_ctx ? enc_ctx->sentence_cap : 0; + + ret = tlt_process_pes_packet( + dec_ctx, + data_node->buffer, + data_node->len, + dec_sub, + sentence_cap); + + /* If Teletext decoding fails with invalid data, abort processing */ + if (ret == CCX_EINVAL) + return ret; + + /* Mark processed byte count */ + got = data_node->len; + } } else if (data_node->bufferdatatype == CCX_PRIVATE_MPEG2_CC) { @@ -1030,11 +1039,11 @@ int process_non_multiprogram_general_loop(struct lib_ccx_ctx *ctx, int general_loop(struct lib_ccx_ctx *ctx) { struct lib_cc_decode *dec_ctx = NULL; - enum ccx_stream_mode_enum stream_mode; + enum ccx_stream_mode_enum stream_mode = CCX_SM_ELEMENTARY_OR_NOT_FOUND; struct demuxer_data *datalist = NULL; struct demuxer_data *data_node = NULL; - int (*get_more_data)(struct lib_ccx_ctx *c, struct demuxer_data **d); - int ret; + int (*get_more_data)(struct lib_ccx_ctx *c, struct demuxer_data **d) = NULL; + int ret = 0; int caps = 0; uint64_t min_pts = UINT64_MAX; @@ -1115,7 +1124,7 @@ int general_loop(struct lib_ccx_ctx *ctx) else { struct cap_info *cinfo = NULL; - struct cap_info *program_iter; + struct cap_info *program_iter = NULL; struct cap_info *ptr = &ctx->demux_ctx->cinfo_tree; struct encoder_ctx *enc_ctx = NULL; list_for_each_entry(program_iter, &ptr->pg_stream, pg_stream, struct cap_info) @@ -1439,6 +1448,8 @@ int rcwt_loop(struct lib_ccx_ctx *ctx) } // end while(1) dbg_print(CCX_DMT_PARSE, "Processed %d bytes\n", bread); + /* Free XDS context - similar to cleanup in general_loop */ + free(dec_ctx->xds_ctx); free(parsebuf); return caps; } diff --git a/src/lib_ccx/lib_ccx.c b/src/lib_ccx/lib_ccx.c index 420cbe9c1..f86c5cefb 100644 --- a/src/lib_ccx/lib_ccx.c +++ b/src/lib_ccx/lib_ccx.c @@ -12,7 +12,7 @@ static struct ccx_decoders_common_settings_t *init_decoder_setting( { struct ccx_decoders_common_settings_t *setting; - setting = malloc(sizeof(struct ccx_decoders_common_settings_t)); + setting = calloc(1, sizeof(struct ccx_decoders_common_settings_t)); if (!setting) return NULL; @@ -33,6 +33,7 @@ static struct ccx_decoders_common_settings_t *init_decoder_setting( setting->hauppauge_mode = opt->hauppauge_mode; setting->xds_write_to_file = opt->transcript_settings.xds; setting->ocr_quantmode = opt->ocr_quantmode; + // program_number, codec, and private_data are zero-initialized by calloc return setting; } diff --git a/src/lib_ccx/telxcc.c b/src/lib_ccx/telxcc.c index 4dff7086f..e28091684 100644 --- a/src/lib_ccx/telxcc.c +++ b/src/lib_ccx/telxcc.c @@ -1516,10 +1516,12 @@ int tlt_process_pes_packet(struct lib_cc_decode *dec_ctx, uint8_t *buffer, uint1 // Called only when teletext is detected or forced and it's going to be used for extraction. void *telxcc_init(void) { - struct TeletextCtx *ctx = malloc(sizeof(struct TeletextCtx)); + // Use calloc to zero-initialize all fields, preventing uninitialized memory errors + struct TeletextCtx *ctx = calloc(1, sizeof(struct TeletextCtx)); if (!ctx) return NULL; + // These memsets are now redundant but kept for clarity memset(ctx->seen_sub_page, 0, MAX_TLT_PAGES * sizeof(short int)); memset(ctx->cc_map, 0, 256); diff --git a/src/lib_ccx/ts_info.c b/src/lib_ccx/ts_info.c index 5eb86d26a..639f2f212 100644 --- a/src/lib_ccx/ts_info.c +++ b/src/lib_ccx/ts_info.c @@ -266,6 +266,8 @@ int update_capinfo(struct ccx_demuxer *ctx, int pid, enum ccx_stream_type stream void dinit_cap(struct ccx_demuxer *ctx) { struct cap_info *iter; + struct lib_ccx_ctx *lctx = (struct lib_ccx_ctx *)ctx->parent; + while (!list_empty(&ctx->cinfo_tree.all_stream)) { iter = list_entry(ctx->cinfo_tree.all_stream.next, struct cap_info, all_stream); @@ -275,12 +277,25 @@ void dinit_cap(struct ccx_demuxer *ctx) // The pointer may have been NULLed by dinit_libraries if it was shared if (iter->codec_private_data) { + void *saved_private_data = iter->codec_private_data; if (iter->codec == CCX_CODEC_DVB) dvbsub_close_decoder(&iter->codec_private_data); else if (iter->codec == CCX_CODEC_TELETEXT) telxcc_close(&iter->codec_private_data, NULL); else free(iter->codec_private_data); + + // Also NULL out any decoder contexts that shared this private_data pointer + // to prevent use-after-free when PAT changes during stream processing + if (lctx) + { + struct lib_cc_decode *dec_ctx; + list_for_each_entry(dec_ctx, &lctx->dec_ctx_head, list, struct lib_cc_decode) + { + if (dec_ctx->private_data == saved_private_data) + dec_ctx->private_data = NULL; + } + } } free(iter); } diff --git a/src/rust/src/common.rs b/src/rust/src/common.rs index fe773627a..9aef909a3 100755 --- a/src/rust/src/common.rs +++ b/src/rust/src/common.rs @@ -4,7 +4,9 @@ use crate::ctorust::FromCType; use crate::demuxer::common_types::{ CapInfo, CcxDemuxReport, CcxRational, PMTEntry, PSIBuffer, ProgramInfo, }; +use crate::utils::free_rust_c_string; use crate::utils::null_pointer; +use crate::utils::replace_rust_c_string; use crate::utils::string_to_c_char; use crate::utils::string_to_c_chars; use lib_ccxr::common::Decoder608Report; @@ -108,7 +110,8 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options (*ccx_s_options).no_progress_bar = options.no_progress_bar as _; if options.sentence_cap_file.try_exists().unwrap_or_default() { - (*ccx_s_options).sentence_cap_file = string_to_c_char( + (*ccx_s_options).sentence_cap_file = replace_rust_c_string( + (*ccx_s_options).sentence_cap_file, options .sentence_cap_file .clone() @@ -127,7 +130,8 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options .try_exists() .unwrap_or_default() { - (*ccx_s_options).filter_profanity_file = string_to_c_char( + (*ccx_s_options).filter_profanity_file = replace_rust_c_string( + (*ccx_s_options).filter_profanity_file, options .filter_profanity_file .clone() @@ -166,16 +170,20 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options (*ccx_s_options).hardsubx = options.hardsubx as _; (*ccx_s_options).hardsubx_and_common = options.hardsubx_and_common as _; if let Some(dvblang) = options.dvblang { - (*ccx_s_options).dvblang = string_to_c_char(dvblang.to_ctype().as_str()); + (*ccx_s_options).dvblang = + replace_rust_c_string((*ccx_s_options).dvblang, dvblang.to_ctype().as_str()); } if let Some(ref ocrlang) = options.ocrlang { - (*ccx_s_options).ocrlang = string_to_c_char(ocrlang.as_str()); + // Cast const to mut for freeing - safe because we allocated this with string_to_c_char + (*ccx_s_options).ocrlang = + replace_rust_c_string((*ccx_s_options).ocrlang as *mut _, ocrlang.as_str()); } (*ccx_s_options).ocr_oem = options.ocr_oem as _; (*ccx_s_options).psm = options.psm as _; (*ccx_s_options).ocr_quantmode = options.ocr_quantmode as _; if let Some(mkvlang) = options.mkvlang { - (*ccx_s_options).mkvlang = string_to_c_char(mkvlang.to_ctype().as_str()); + (*ccx_s_options).mkvlang = + replace_rust_c_string((*ccx_s_options).mkvlang, mkvlang.to_ctype().as_str()); } (*ccx_s_options).analyze_video_stream = options.analyze_video_stream as _; (*ccx_s_options).hardsubx_ocr_mode = options.hardsubx_ocr_mode.to_ctype(); @@ -193,34 +201,57 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options (*ccx_s_options).debug_mask = options.debug_mask.normal_mask().bits() as _; (*ccx_s_options).debug_mask_on_debug = options.debug_mask.debug_mask().bits() as _; if options.udpsrc.is_some() { - (*ccx_s_options).udpsrc = string_to_c_char(&options.udpsrc.clone().unwrap()); + (*ccx_s_options).udpsrc = + replace_rust_c_string((*ccx_s_options).udpsrc, &options.udpsrc.clone().unwrap()); } if options.udpaddr.is_some() { - (*ccx_s_options).udpaddr = string_to_c_char(&options.udpaddr.clone().unwrap()); + (*ccx_s_options).udpaddr = + replace_rust_c_string((*ccx_s_options).udpaddr, &options.udpaddr.clone().unwrap()); } (*ccx_s_options).udpport = options.udpport as _; if options.tcpport.is_some() { - (*ccx_s_options).tcpport = string_to_c_char(&options.tcpport.unwrap().to_string()); + (*ccx_s_options).tcpport = replace_rust_c_string( + (*ccx_s_options).tcpport, + &options.tcpport.unwrap().to_string(), + ); } if options.tcp_password.is_some() { - (*ccx_s_options).tcp_password = string_to_c_char(&options.tcp_password.clone().unwrap()); + (*ccx_s_options).tcp_password = replace_rust_c_string( + (*ccx_s_options).tcp_password, + &options.tcp_password.clone().unwrap(), + ); } if options.tcp_desc.is_some() { - (*ccx_s_options).tcp_desc = string_to_c_char(&options.tcp_desc.clone().unwrap()); + (*ccx_s_options).tcp_desc = replace_rust_c_string( + (*ccx_s_options).tcp_desc, + &options.tcp_desc.clone().unwrap(), + ); } if options.srv_addr.is_some() { - (*ccx_s_options).srv_addr = string_to_c_char(&options.srv_addr.clone().unwrap()); + (*ccx_s_options).srv_addr = replace_rust_c_string( + (*ccx_s_options).srv_addr, + &options.srv_addr.clone().unwrap(), + ); } if options.srv_port.is_some() { - (*ccx_s_options).srv_port = string_to_c_char(&options.srv_port.unwrap().to_string()); + (*ccx_s_options).srv_port = replace_rust_c_string( + (*ccx_s_options).srv_port, + &options.srv_port.unwrap().to_string(), + ); } (*ccx_s_options).noautotimeref = options.noautotimeref as _; (*ccx_s_options).input_source = options.input_source as _; if options.output_filename.is_some() { - (*ccx_s_options).output_filename = - string_to_c_char(&options.output_filename.clone().unwrap()); + (*ccx_s_options).output_filename = replace_rust_c_string( + (*ccx_s_options).output_filename, + &options.output_filename.clone().unwrap(), + ); } - if options.inputfile.is_some() { + // Only set inputfile if it's not already set (first call from ccxr_parse_parameters). + // Subsequent calls from ccxr_demuxer_open/close should NOT modify inputfile because + // C code holds references to those strings throughout processing. + // Freeing them would cause use-after-free and double-free errors. + if options.inputfile.is_some() && (*ccx_s_options).inputfile.is_null() { (*ccx_s_options).inputfile = string_to_c_chars(options.inputfile.clone().unwrap()); (*ccx_s_options).num_input_files = options .inputfile @@ -231,7 +262,12 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options .count() as _; } (*ccx_s_options).demux_cfg = options.demux_cfg.to_ctype(); - (*ccx_s_options).enc_cfg = options.enc_cfg.to_ctype(); + // Only set enc_cfg on the first call (when output_filename is null). + // Subsequent calls from ccxr_demuxer_open/close should NOT modify enc_cfg + // because it causes memory leaks (strings allocated but never freed). + if (*ccx_s_options).enc_cfg.output_filename.is_null() { + (*ccx_s_options).enc_cfg = options.enc_cfg.to_ctype(); + } (*ccx_s_options).subs_delay = options.subs_delay.millis(); (*ccx_s_options).cc_to_stdout = options.cc_to_stdout as _; (*ccx_s_options).pes_header_to_stdout = options.pes_header_to_stdout as _; @@ -242,8 +278,10 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options #[cfg(feature = "with_libcurl")] { if options.curlposturl.is_some() { - (*ccx_s_options).curlposturl = - string_to_c_char(&options.curlposturl.as_ref().unwrap_or_default().as_str()); + (*ccx_s_options).curlposturl = replace_rust_c_string( + (*ccx_s_options).curlposturl, + options.curlposturl.as_ref().unwrap_or_default().as_str(), + ); } } } @@ -842,6 +880,24 @@ impl CType<[u32; 128]> for Vec { } } +/// Free all Rust-allocated strings in an encoder_cfg struct. +/// This must be called before overwriting enc_cfg with a new value to avoid memory leaks. +/// # Safety +/// The string pointers must have been allocated by Rust's `string_to_c_char` or be null. +pub unsafe fn free_encoder_cfg_strings(cfg: &encoder_cfg) { + free_rust_c_string(cfg.output_filename); + free_rust_c_string(cfg.start_credits_text); + free_rust_c_string(cfg.end_credits_text); + free_rust_c_string(cfg.first_input_file); + free_rust_c_string(cfg.render_font); + free_rust_c_string(cfg.render_font_italics); + free_rust_c_string(cfg.all_services_charset); + #[cfg(feature = "with_libcurl")] + free_rust_c_string(cfg.curlposturl); + // Note: services_charsets is a *mut *mut c_char (array of strings) + // which would need special handling, but it's typically null in practice +} + impl CType for EncoderConfig { unsafe fn to_ctype(&self) -> encoder_cfg { encoder_cfg { diff --git a/src/rust/src/decoder/mod.rs b/src/rust/src/decoder/mod.rs index 5534129ed..92e608cb8 100644 --- a/src/rust/src/decoder/mod.rs +++ b/src/rust/src/decoder/mod.rs @@ -45,10 +45,13 @@ impl<'a> Dtvcc<'a> { /// Create a new dtvcc context pub fn new(ctx: &'a mut dtvcc_ctx) -> Self { let report = unsafe { &mut *ctx.report }; - let mut encoder = Box::into_raw(Box::new(encoder_ctx::default())); - if !ctx.encoder.is_null() { - encoder = unsafe { &mut *(ctx.encoder as *mut encoder_ctx) }; - } + // Only allocate a new encoder if ctx.encoder is null + // Previously this always allocated then threw away the allocation if not needed + let encoder = if ctx.encoder.is_null() { + Box::into_raw(Box::new(encoder_ctx::default())) + } else { + ctx.encoder as *mut encoder_ctx + }; let timing = unsafe { &mut *ctx.timing }; Self { diff --git a/src/rust/src/demuxer/common_types.rs b/src/rust/src/demuxer/common_types.rs index 99df21f7c..9fa0d2683 100644 --- a/src/rust/src/demuxer/common_types.rs +++ b/src/rust/src/demuxer/common_types.rs @@ -268,6 +268,34 @@ impl Default for CcxDemuxer<'_> { } } } + +/// Drop implementation to free Rust-owned allocations in pid_buffers and pids_programs. +/// When CcxDemuxer is created via copy_demuxer_from_c_to_rust, these arrays contain +/// Rust-owned Box pointers that must be freed. When created via Default, they contain +/// null pointers which are safely ignored. +impl Drop for CcxDemuxer<'_> { + fn drop(&mut self) { + // Free all non-null PSIBuffer pointers (Rust-owned from Box::into_raw) + for ptr in self.pid_buffers.drain(..) { + if !ptr.is_null() { + // SAFETY: These pointers were created via Box::into_raw in copy_demuxer_from_c_to_rust + unsafe { + drop(Box::from_raw(ptr)); + } + } + } + // Free all non-null PMTEntry pointers (Rust-owned from Box::into_raw) + for ptr in self.pids_programs.drain(..) { + if !ptr.is_null() { + // SAFETY: These pointers were created via Box::into_raw in copy_demuxer_from_c_to_rust + unsafe { + drop(Box::from_raw(ptr)); + } + } + } + } +} + impl Default for ProgramInfo { fn default() -> Self { ProgramInfo { diff --git a/src/rust/src/demuxer/demux.rs b/src/rust/src/demuxer/demux.rs index 209a43d30..ddfd9bf09 100755 --- a/src/rust/src/demuxer/demux.rs +++ b/src/rust/src/demuxer/demux.rs @@ -473,17 +473,53 @@ mod tests { } fn dummy_demuxer<'a>() -> CcxDemuxer<'a> { + // Can't use ..Default::default() because CcxDemuxer implements Drop CcxDemuxer { + infd: -1, + past: 0, + m2ts: 0, + auto_stream: StreamMode::ElementaryOrNotFound, + stream_mode: StreamMode::ElementaryOrNotFound, + ts_autoprogram: false, + ts_allprogram: false, + flag_ts_forced_pn: false, + ts_datastreamtype: StreamType::Unknownstream, + pinfo: Vec::new(), + nb_program: 0, + codec: Codec::Any, + flag_ts_forced_cappid: false, + nocodec: Codec::Any, + cinfo_tree: CapInfo::default(), + startbytes: vec![0; STARTBYTESLENGTH], + startbytes_pos: 0, + startbytes_avail: 0, + global_timestamp: Timestamp::from_millis(0), + min_global_timestamp: Timestamp::from_millis(0), + offset_global_timestamp: Timestamp::from_millis(0), + last_global_timestamp: Timestamp::from_millis(0), + global_timestamp_inited: Timestamp::from_millis(0), + pid_buffers: vec![], + pids_seen: vec![], + stream_id_of_each_pid: vec![], + min_pts: vec![0; MAX_PSI_PID + 1], + have_pids: vec![], + num_of_pids: 0, + pids_programs: vec![], + freport: CcxDemuxReport::default(), + hauppauge_warning_shown: false, + multi_stream_per_prog: 0, + last_pat_payload: null_mut(), + last_pat_length: 0, filebuffer: null_mut(), filebuffer_start: 999, filebuffer_pos: 999, bytesinbuffer: 999, - have_pids: vec![], - pids_seen: vec![], - min_pts: vec![0; MAX_PSI_PID + 1], - stream_id_of_each_pid: vec![], - pids_programs: vec![], - ..Default::default() + warning_program_not_found_shown: false, + strangeheader: 0, + parent: None, + private_data: null_mut(), + #[cfg(feature = "enable_ffmpeg")] + ffmpeg_ctx: null_mut(), } } diff --git a/src/rust/src/libccxr_exports/demuxer.rs b/src/rust/src/libccxr_exports/demuxer.rs index 03d55c17d..5cbb66034 100755 --- a/src/rust/src/libccxr_exports/demuxer.rs +++ b/src/rust/src/libccxr_exports/demuxer.rs @@ -1,4 +1,4 @@ -use crate::bindings::{ccx_demuxer, lib_ccx_ctx}; +use crate::bindings::{ccx_datasource_CCX_DS_FILE, ccx_demuxer, lib_ccx_ctx}; use crate::ccx_options; use crate::common::{copy_from_rust, copy_to_rust, CType}; use crate::ctorust::FromCType; @@ -11,6 +11,12 @@ use std::alloc::{alloc_zeroed, Layout}; use std::ffi::CStr; use std::os::raw::{c_char, c_int, c_longlong, c_uchar, c_uint, c_void}; +// External C function declarations +extern "C" { + fn activity_input_file_closed(); + fn close(fd: c_int) -> c_int; +} + pub fn copy_c_array_to_rust_vec( c_bytes: &[u8; crate::demuxer::common_types::ARRAY_SIZE], ) -> Vec { @@ -385,11 +391,15 @@ pub unsafe extern "C" fn ccxr_demuxer_close(ctx: *mut ccx_demuxer) { if ctx.is_null() { return; } - let mut demux_ctx = copy_demuxer_from_c_to_rust(ctx); - let mut CcxOptions: Options = copy_to_rust(&raw const ccx_options); - demux_ctx.close(&mut CcxOptions); - copy_from_rust(&raw mut ccx_options, CcxOptions); - copy_demuxer_from_rust_to_c(ctx, &demux_ctx); + // Work directly on the C struct to avoid memory allocations from copy operations + let c = &mut *ctx; + c.past = 0; + if c.infd != -1 && ccx_options.input_source == ccx_datasource_CCX_DS_FILE { + // Close the file descriptor using the C library close function + close(c.infd); + c.infd = -1; + activity_input_file_closed(); + } } // Extern function for ccx_demuxer_isopen @@ -400,8 +410,9 @@ pub unsafe extern "C" fn ccxr_demuxer_isopen(ctx: *mut ccx_demuxer) -> c_int { if ctx.is_null() { return 0; } - let demux_ctx = copy_demuxer_from_c_to_rust(ctx); - if demux_ctx.is_open() { + // Directly check infd instead of copying the entire structure + // This avoids memory allocations that would leak + if (*ctx).infd != -1 { 1 } else { 0 diff --git a/src/rust/src/utils.rs b/src/rust/src/utils.rs index 90ada59dd..4b531e74b 100644 --- a/src/rust/src/utils.rs +++ b/src/rust/src/utils.rs @@ -34,6 +34,31 @@ pub fn string_to_c_char(a: &str) -> *mut ::std::os::raw::c_char { s.into_raw() } +/// Free a C string that was allocated by Rust's `string_to_c_char` function. +/// # Safety +/// The pointer must have been allocated by `string_to_c_char` (i.e., `CString::into_raw`) +/// or be null. Passing a pointer allocated by C's malloc will cause undefined behavior. +pub unsafe fn free_rust_c_string(ptr: *mut ::std::os::raw::c_char) { + if !ptr.is_null() { + // Reclaim ownership and drop the CString, which frees the memory + let _ = ffi::CString::from_raw(ptr); + } +} + +/// Replace a C string pointer with a new one, freeing the old string if it was +/// allocated by Rust. Returns the new pointer. +/// # Safety +/// The old pointer must have been allocated by `string_to_c_char` or be null. +pub unsafe fn replace_rust_c_string( + old: *mut ::std::os::raw::c_char, + new_str: &str, +) -> *mut ::std::os::raw::c_char { + // Free the old string if it exists + free_rust_c_string(old); + // Allocate and return the new string + string_to_c_char(new_str) +} + /// # Safety /// The pointer returned has to be deallocated using from_raw() at some point pub fn null_pointer() -> *mut T { @@ -52,6 +77,26 @@ pub fn string_to_c_chars(strs: Vec) -> *mut *mut c_char { ptr } +/// Free an array of C strings that was allocated by `string_to_c_chars`. +/// # Safety +/// The pointers must have been allocated by `string_to_c_chars` or be null. +/// `count` must be the number of strings in the array. +pub unsafe fn free_rust_c_string_array(arr: *mut *mut c_char, count: usize) { + if arr.is_null() { + return; + } + // Free each string in the array + for i in 0..count { + let str_ptr = *arr.add(i); + free_rust_c_string(str_ptr); + } + // Free the array itself by reconstructing the Vec and dropping it + // Note: We assume the Vec was created with exactly `count` elements + if count > 0 { + let _ = Vec::from_raw_parts(arr, count, count); + } +} + /// This function creates a new object of type `T` and fills it with zeros. /// /// This function uses the `std::alloc::alloc_zeroed` function to allocate