Skip to content

Commit 23d9e5f

Browse files
committed
runtime(bootstrap): preserve profiled trunking and disable profiled autosave
1 parent 397aa90 commit 23d9e5f

2 files changed

Lines changed: 191 additions & 20 deletions

File tree

src/runtime/bootstrap/bootstrap.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ dsd_runtime_bootstrap(int argc, char** argv, dsd_opts* opts, dsd_state* state, i
115115
const dsdneoRuntimeConfig* rcfg = dsd_neo_get_config();
116116
const char* config_env = (rcfg && rcfg->config_path_is_set) ? rcfg->config_path : NULL;
117117

118+
const int explicit_profile_selected = (profile_cli && *profile_cli) ? 1 : 0;
118119
int user_cfg_loaded = 0;
119120
dsdneoUserConfig user_cfg;
120121
memset(&user_cfg, 0, sizeof user_cfg);
@@ -137,7 +138,8 @@ dsd_runtime_bootstrap(int argc, char** argv, dsd_opts* opts, dsd_state* state, i
137138
}
138139

139140
if (cfg_path && *cfg_path) {
140-
// Remember the path so we can autosave the effective config later.
141+
// Remember the path for config-aware frontends and, when safe,
142+
// for autosaving the effective config later.
141143
state->config_autosave_enabled = 1;
142144
snprintf(state->config_autosave_path, sizeof state->config_autosave_path, "%s", cfg_path);
143145
state->config_autosave_path[sizeof state->config_autosave_path - 1] = '\0';
@@ -152,12 +154,19 @@ dsd_runtime_bootstrap(int argc, char** argv, dsd_opts* opts, dsd_state* state, i
152154
if (load_rc == 0) {
153155
dsd_apply_user_config_to_opts(&user_cfg, opts, state);
154156
user_cfg_loaded = 1;
155-
if (profile_cli && *profile_cli) {
157+
if (explicit_profile_selected) {
158+
/* Saving the effective config would flatten the profiled
159+
view back into the base INI and destroy profile sections.
160+
Keep the source path for explicit user actions, but
161+
disable automatic writes for this run. */
162+
state->config_autosave_enabled = 0;
163+
LOG_NOTICE("Autosave disabled for profiled config %s to avoid overwriting profile sections.\n",
164+
cfg_path);
156165
LOG_NOTICE("Loaded user config from %s (profile: %s)\n", cfg_path, profile_cli);
157166
} else {
158167
LOG_NOTICE("Loaded user config from %s\n", cfg_path);
159168
}
160-
} else if (profile_cli && *profile_cli) {
169+
} else if (explicit_profile_selected) {
161170
// Missing profile is fatal when --profile is specified
162171
LOG_ERROR("Profile '%s' not found in config file %s\n", profile_cli, cfg_path);
163172
bootstrap_set_exit_rc(out_exit_rc, 1);
@@ -205,10 +214,10 @@ dsd_runtime_bootstrap(int argc, char** argv, dsd_opts* opts, dsd_state* state, i
205214
// If a user config enabled trunking but this process was started with
206215
// any effective CLI arguments and none of them explicitly enabled/disabled
207216
// trunk (via -T / -Y), fall back to the built-in default of trunking
208-
// disabled for this run. This keeps CLI-driven sessions from inheriting
209-
// trunk enable solely from the config file, while allowing config-driven
210-
// runs like: dsd-neo --config <path>
211-
if (argc_effective > 1 && user_cfg_loaded && !opts->trunk_cli_seen) {
217+
// disabled for this run. Explicit profile selection counts as an
218+
// intentional request to use that profiled config, so preserve its
219+
// trunking state even when additional frontend/UI flags are present.
220+
if (argc_effective > 1 && user_cfg_loaded && !opts->trunk_cli_seen && !explicit_profile_selected) {
212221
opts->p25_trunk = 0;
213222
opts->trunk_enable = 0;
214223
}

tests/runtime/test_runtime_cli_parse.c

Lines changed: 175 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ test_path_sep(void) {
367367
}
368368

369369
static int
370-
test_create_temp_ini(char* out_path, size_t out_path_size) {
371-
if (!out_path || out_path_size == 0) {
370+
test_create_temp_ini_with_contents(const char* contents, char* out_path, size_t out_path_size) {
371+
if (!contents || !out_path || out_path_size == 0) {
372372
return -1;
373373
}
374374

@@ -405,21 +405,28 @@ test_create_temp_ini(char* out_path, size_t out_path_size) {
405405
return -1;
406406
}
407407

408-
fputs("version = 1\n"
409-
"\n"
410-
"[input]\n"
411-
"source = \"rtl\"\n"
412-
"rtl_device = 0\n"
413-
"rtl_freq = \"100000000\"\n"
414-
"\n"
415-
"[trunking]\n"
416-
"enabled = true\n",
417-
fp);
418-
408+
fputs(contents, fp);
419409
fclose(fp);
420410
return 0;
421411
}
422412

413+
static int
414+
test_create_temp_ini(char* out_path, size_t out_path_size) {
415+
if (!out_path || out_path_size == 0) {
416+
return -1;
417+
}
418+
return test_create_temp_ini_with_contents("version = 1\n"
419+
"\n"
420+
"[input]\n"
421+
"source = \"rtl\"\n"
422+
"rtl_device = 0\n"
423+
"rtl_freq = \"100000000\"\n"
424+
"\n"
425+
"[trunking]\n"
426+
"enabled = true\n",
427+
out_path, out_path_size);
428+
}
429+
423430
static int
424431
test_create_temp_vertex_ks_csv(char* out_path, size_t out_path_size, int malformed) {
425432
if (!out_path || out_path_size == 0) {
@@ -617,6 +624,159 @@ test_bootstrap_print_config_normalizes_soapy_shorthand(void) {
617624
return test_rc;
618625
}
619626

627+
static int
628+
test_bootstrap_profile_preserves_trunking_with_ncurses_cli(void) {
629+
dsd_opts* opts = (dsd_opts*)calloc(1, sizeof(dsd_opts));
630+
dsd_state* state = (dsd_state*)calloc(1, sizeof(dsd_state));
631+
if (!opts || !state) {
632+
free(opts);
633+
free(state);
634+
fprintf(stderr, "out of memory\n");
635+
return 1;
636+
}
637+
638+
initOpts(opts);
639+
initState(state);
640+
641+
(void)dsd_unsetenv("DSD_NEO_CONFIG");
642+
(void)dsd_setenv("DSD_NEO_NO_BOOTSTRAP", "1", 1);
643+
644+
static const char* ini = "version = 1\n"
645+
"\n"
646+
"[input]\n"
647+
"source = \"pulse\"\n"
648+
"\n"
649+
"[profile.p25_trunk]\n"
650+
"input.source = \"rtl\"\n"
651+
"input.rtl_device = 0\n"
652+
"input.rtl_freq = \"100000000\"\n"
653+
"trunking.enabled = true\n";
654+
655+
char cfg_path[1024];
656+
if (test_create_temp_ini_with_contents(ini, cfg_path, sizeof cfg_path) != 0) {
657+
fprintf(stderr, "failed to create temp profile ini\n");
658+
freeState(state);
659+
free(opts);
660+
free(state);
661+
return 1;
662+
}
663+
664+
char arg0[] = "dsd-neo";
665+
char arg1[] = "--config";
666+
char arg2[1024];
667+
char arg3[] = "--profile";
668+
char arg4[] = "p25_trunk";
669+
char arg5[] = "-N";
670+
snprintf(arg2, sizeof arg2, "%s", cfg_path);
671+
char* argv[] = {arg0, arg1, arg2, arg3, arg4, arg5, NULL};
672+
673+
int argc_effective = 0;
674+
int exit_rc = -1;
675+
int rc = dsd_runtime_bootstrap(6, argv, opts, state, &argc_effective, &exit_rc);
676+
if (rc != DSD_BOOTSTRAP_CONTINUE) {
677+
fprintf(stderr, "expected rc=%d, got %d (exit_rc=%d)\n", DSD_BOOTSTRAP_CONTINUE, rc, exit_rc);
678+
(void)remove(cfg_path);
679+
freeState(state);
680+
free(opts);
681+
free(state);
682+
return 1;
683+
}
684+
685+
int test_rc = 0;
686+
if (opts->trunk_enable != 1 || opts->p25_trunk != 1) {
687+
fprintf(stderr, "expected profiled trunking to stay enabled, got trunk_enable=%d p25_trunk=%d\n",
688+
opts->trunk_enable, opts->p25_trunk);
689+
test_rc = 1;
690+
}
691+
if (opts->use_ncurses_terminal != 1) {
692+
fprintf(stderr, "expected -N to remain applied, got use_ncurses_terminal=%d\n", opts->use_ncurses_terminal);
693+
test_rc = 1;
694+
}
695+
if (strncmp(opts->audio_in_dev, "rtl:", 4) != 0) {
696+
fprintf(stderr, "expected profile RTL input, got audio_in_dev=%s\n", opts->audio_in_dev);
697+
test_rc = 1;
698+
}
699+
700+
(void)remove(cfg_path);
701+
freeState(state);
702+
free(opts);
703+
free(state);
704+
return test_rc;
705+
}
706+
707+
static int
708+
test_bootstrap_profile_disables_autosave(void) {
709+
dsd_opts* opts = (dsd_opts*)calloc(1, sizeof(dsd_opts));
710+
dsd_state* state = (dsd_state*)calloc(1, sizeof(dsd_state));
711+
if (!opts || !state) {
712+
free(opts);
713+
free(state);
714+
fprintf(stderr, "out of memory\n");
715+
return 1;
716+
}
717+
718+
initOpts(opts);
719+
initState(state);
720+
721+
(void)dsd_unsetenv("DSD_NEO_CONFIG");
722+
(void)dsd_setenv("DSD_NEO_NO_BOOTSTRAP", "1", 1);
723+
724+
static const char* ini = "version = 1\n"
725+
"\n"
726+
"[profile.p25_trunk]\n"
727+
"input.source = \"rtl\"\n"
728+
"input.rtl_device = 0\n"
729+
"input.rtl_freq = \"100000000\"\n"
730+
"trunking.enabled = true\n";
731+
732+
char cfg_path[1024];
733+
if (test_create_temp_ini_with_contents(ini, cfg_path, sizeof cfg_path) != 0) {
734+
fprintf(stderr, "failed to create temp profile ini\n");
735+
freeState(state);
736+
free(opts);
737+
free(state);
738+
return 1;
739+
}
740+
741+
char arg0[] = "dsd-neo";
742+
char arg1[] = "--config";
743+
char arg2[1024];
744+
char arg3[] = "--profile";
745+
char arg4[] = "p25_trunk";
746+
snprintf(arg2, sizeof arg2, "%s", cfg_path);
747+
char* argv[] = {arg0, arg1, arg2, arg3, arg4, NULL};
748+
749+
int argc_effective = 0;
750+
int exit_rc = -1;
751+
int rc = dsd_runtime_bootstrap(5, argv, opts, state, &argc_effective, &exit_rc);
752+
if (rc != DSD_BOOTSTRAP_CONTINUE) {
753+
fprintf(stderr, "expected rc=%d, got %d (exit_rc=%d)\n", DSD_BOOTSTRAP_CONTINUE, rc, exit_rc);
754+
(void)remove(cfg_path);
755+
freeState(state);
756+
free(opts);
757+
free(state);
758+
return 1;
759+
}
760+
761+
int test_rc = 0;
762+
if (state->config_autosave_enabled != 0) {
763+
fprintf(stderr, "expected autosave disabled for profiled config, got enabled=%d\n",
764+
state->config_autosave_enabled);
765+
test_rc = 1;
766+
}
767+
if (strcmp(state->config_autosave_path, cfg_path) != 0) {
768+
fprintf(stderr, "expected profiled config path retained as %s, got %s\n", cfg_path,
769+
state->config_autosave_path);
770+
test_rc = 1;
771+
}
772+
773+
(void)remove(cfg_path);
774+
freeState(state);
775+
free(opts);
776+
free(state);
777+
return test_rc;
778+
}
779+
620780
static int
621781
test_r_playback_optind_is_first_file_regardless_of_option_order(void) {
622782
int test_rc = 0;
@@ -1549,6 +1709,8 @@ main(void) {
15491709
rc |= test_1_loads_rc4_key_allows_0x_prefix();
15501710
rc |= test_bootstrap_treats_lone_ini_as_config();
15511711
rc |= test_bootstrap_print_config_normalizes_soapy_shorthand();
1712+
rc |= test_bootstrap_profile_preserves_trunking_with_ncurses_cli();
1713+
rc |= test_bootstrap_profile_disables_autosave();
15521714
rc |= test_r_playback_optind_is_first_file_regardless_of_option_order();
15531715
rc |= test_open_mbe_missing_file_leaves_stream_null();
15541716
rc |= test_rdio_long_options_parse();

0 commit comments

Comments
 (0)