1818#include < cstdlib>
1919#include < cstring>
2020#include < dirent.h>
21+ #include < memory>
2122#include < string>
2223#include < sys/stat.h>
2324#include < vector>
@@ -251,6 +252,13 @@ static std::string find_nested_directory(const char* extracted_dir) {
251252 return visible_dirs[0 ];
252253 }
253254
255+ if (visible_dirs.size () > 1 ) {
256+ RAC_LOG_WARNING (LOG_TAG,
257+ " find_nested_directory: found %zu subdirectories in '%s', "
258+ " falling back to root (expected exactly 1)" ,
259+ visible_dirs.size (), extracted_dir);
260+ }
261+
254262 return extracted_dir;
255263}
256264
@@ -283,25 +291,46 @@ struct orchestrate_context {
283291 void * user_data;
284292};
285293
294+ /* *
295+ * Prevent double-free of orchestrate_context when async callbacks race with error paths.
296+ *
297+ * The context is wrapped in a shared_ptr stored in a shared_ctx_holder.
298+ * The holder is passed as raw void* to C callbacks.
299+ * Both the caller and the callback own a reference via the shared_ptr,
300+ * ensuring the context outlives all users.
301+ */
302+ struct shared_ctx_holder {
303+ std::shared_ptr<orchestrate_context> ctx;
304+ };
305+
286306/* *
287307 * HTTP progress callback — forwards to download manager which recalculates overall progress.
288308 */
289309static void orchestrate_http_progress (int64_t bytes_downloaded, int64_t total_bytes,
290310 void * callback_user_data) {
291- auto * ctx = static_cast <orchestrate_context *>(callback_user_data);
292- if (!ctx || !ctx->dm_handle ) return ;
311+ auto * holder = static_cast <shared_ctx_holder *>(callback_user_data);
312+ if (!holder || !holder-> ctx || !holder-> ctx ->dm_handle ) return ;
293313
314+ auto & ctx = holder->ctx ;
294315 rac_download_manager_update_progress (ctx->dm_handle , ctx->task_id .c_str (), bytes_downloaded,
295316 total_bytes);
296317}
297318
298319/* *
299320 * HTTP completion callback — handles post-download extraction and cleanup.
321+ * Deletes the holder (releasing its shared_ptr reference) when done.
300322 */
301323static void orchestrate_http_complete (rac_result_t result, const char * downloaded_path,
302324 void * callback_user_data) {
303- auto * ctx = static_cast <orchestrate_context*>(callback_user_data);
304- if (!ctx) return ;
325+ auto * holder = static_cast <shared_ctx_holder*>(callback_user_data);
326+ if (!holder || !holder->ctx ) {
327+ delete holder;
328+ return ;
329+ }
330+
331+ // Take ownership — holder is deleted at every exit path below
332+ auto ctx = holder->ctx ;
333+ delete holder;
305334
306335 if (result != RAC_SUCCESS) {
307336 // HTTP download failed
@@ -312,7 +341,6 @@ static void orchestrate_http_complete(rac_result_t result, const char* downloade
312341 if (ctx->user_complete_callback ) {
313342 ctx->user_complete_callback (ctx->task_id .c_str (), result, nullptr , ctx->user_data );
314343 }
315- delete ctx;
316344 return ;
317345 }
318346
@@ -345,7 +373,6 @@ static void orchestrate_http_complete(rac_result_t result, const char* downloade
345373
346374 // Cleanup temp archive
347375 delete_file (ctx->download_dest_path .c_str ());
348- delete ctx;
349376 return ;
350377 }
351378
@@ -392,8 +419,6 @@ static void orchestrate_http_complete(rac_result_t result, const char* downloade
392419 ctx->user_complete_callback (ctx->task_id .c_str (), RAC_SUCCESS, final_path.c_str (),
393420 ctx->user_data );
394421 }
395-
396- delete ctx;
397422}
398423
399424// =============================================================================
@@ -469,8 +494,8 @@ rac_result_t rac_download_orchestrate(rac_download_manager_handle_t dm_handle,
469494 return start_result;
470495 }
471496
472- // 5. Create orchestration context for callbacks
473- auto * ctx = new orchestrate_context ();
497+ // 5. Create orchestration context for callbacks (shared_ptr for safe async lifetime)
498+ auto ctx = std::make_shared< orchestrate_context> ();
474499 ctx->dm_handle = dm_handle;
475500 ctx->model_id = model_id;
476501 ctx->download_url = download_url;
@@ -485,17 +510,20 @@ rac_result_t rac_download_orchestrate(rac_download_manager_handle_t dm_handle,
485510 ctx->user_complete_callback = complete_callback;
486511 ctx->user_data = user_data;
487512
513+ // Wrap in holder for C callback void* — callback takes ownership and deletes holder
514+ auto * holder = new shared_ctx_holder{ctx};
515+
488516 // 6. Start HTTP download via platform adapter
489517 char * http_task_id = nullptr ;
490518 rac_result_t http_result =
491519 rac_http_download (download_url, download_dest.c_str (), orchestrate_http_progress,
492- orchestrate_http_complete, ctx , &http_task_id);
520+ orchestrate_http_complete, holder , &http_task_id);
493521
494522 if (http_result != RAC_SUCCESS) {
495523 RAC_LOG_ERROR (LOG_TAG, " Failed to start HTTP download for: %s" , model_id);
496524 rac_download_manager_mark_failed (dm_handle, task_id, http_result,
497525 " Failed to start HTTP download" );
498- delete ctx;
526+ delete holder; // Safe — ctx shared_ptr ref still alive until scope exit
499527 rac_free (task_id);
500528 return http_result;
501529 }
@@ -583,26 +611,37 @@ rac_result_t rac_download_orchestrate_multi(
583611 // For now we use the platform adapter's synchronous path.
584612 char * http_task_id = nullptr ;
585613
586- // For multi-file we need a simpler blocking approach
587- // The complete callback will be called by the platform when done
614+ // For multi-file we need a simpler blocking approach.
615+ // Use shared_ptr to prevent double-free if callback fires unexpectedly.
588616 struct multi_file_ctx {
589617 bool completed;
590618 rac_result_t result;
591619 std::string downloaded_path;
592620 };
593621
594- auto * file_ctx = new multi_file_ctx{false , RAC_SUCCESS, " " };
622+ auto file_ctx = std::make_shared<multi_file_ctx>(multi_file_ctx{false , RAC_SUCCESS, " " });
623+
624+ // Wrap in a raw holder for C callback void* — callback takes ownership of the holder
625+ struct multi_file_holder {
626+ std::shared_ptr<multi_file_ctx> ctx;
627+ };
628+ auto * file_holder = new multi_file_holder{file_ctx};
595629
596630 auto file_complete = [](rac_result_t result, const char * path, void * ud) {
597- auto * fctx = static_cast <multi_file_ctx*>(ud);
598- fctx->completed = true ;
599- fctx->result = result;
600- if (path) fctx->downloaded_path = path;
631+ auto * holder = static_cast <multi_file_holder*>(ud);
632+ if (!holder || !holder->ctx ) {
633+ delete holder;
634+ return ;
635+ }
636+ holder->ctx ->completed = true ;
637+ holder->ctx ->result = result;
638+ if (path) holder->ctx ->downloaded_path = path;
639+ delete holder;
601640 };
602641
603642 rac_result_t http_result = rac_http_download (
604643 file_url.c_str (), dest_path.c_str (), nullptr /* no per-file progress */ , file_complete,
605- file_ctx , &http_task_id);
644+ file_holder , &http_task_id);
606645
607646 if (http_task_id) rac_free (http_task_id);
608647
@@ -611,19 +650,18 @@ rac_result_t rac_download_orchestrate_multi(
611650 any_failed = true ;
612651 RAC_LOG_ERROR (LOG_TAG, " Required file download failed to start: %s" ,
613652 file.relative_path );
614- delete file_ctx;
653+ // Download never started, so callback won't fire — delete holder safely
654+ delete file_holder;
615655 break ;
616656 }
617657 RAC_LOG_WARNING (LOG_TAG, " Optional file download failed to start: %s" ,
618658 file.relative_path );
619- delete file_ctx;
659+ // Download never started, so callback won't fire — delete holder safely
660+ delete file_holder;
620661 continue ;
621662 }
622663
623- // Note: The HTTP download is asynchronous. For multi-file orchestration,
624- // the platform SDK should handle sequencing. This is a best-effort sequential trigger.
625- // The actual completion will be handled by the platform's async dispatch.
626- delete file_ctx;
664+ // Download started — async callback will delete file_holder
627665 }
628666
629667 if (any_failed) {
0 commit comments