Skip to content

Commit 0ce44e2

Browse files
pks-tgitster
authored andcommitted
builtin/gc: fix leaking config values
We're leaking config values in git-gc(1) when those values are tracked as strings. Introduce a new `gc_config_release()` function that releases this memory to plug those leaks and release old values before populating the config fields via `git_config_string()` et al. Note that there is one small gotcha here with the "--prune" option. Next to passing a string, this option also accepts the "--no-prune" option that overrides the default or configured value. We thus need to discern between the option not having been passed by the user and the negative variant of it. This is done by using a simple sentinel value that lets us discern these cases. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d1ae15d commit 0ce44e2

File tree

2 files changed

+82
-27
lines changed

2 files changed

+82
-27
lines changed

builtin/gc.c

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ struct gc_config {
139139
int gc_auto_threshold;
140140
int gc_auto_pack_limit;
141141
int detach_auto;
142-
const char *gc_log_expire;
143-
const char *prune_expire;
144-
const char *prune_worktrees_expire;
142+
char *gc_log_expire;
143+
char *prune_expire;
144+
char *prune_worktrees_expire;
145145
char *repack_filter;
146146
char *repack_filter_to;
147147
unsigned long big_pack_threshold;
@@ -157,15 +157,25 @@ struct gc_config {
157157
.gc_auto_threshold = 6700, \
158158
.gc_auto_pack_limit = 50, \
159159
.detach_auto = 1, \
160-
.gc_log_expire = "1.day.ago", \
161-
.prune_expire = "2.weeks.ago", \
162-
.prune_worktrees_expire = "3.months.ago", \
160+
.gc_log_expire = xstrdup("1.day.ago"), \
161+
.prune_expire = xstrdup("2.weeks.ago"), \
162+
.prune_worktrees_expire = xstrdup("3.months.ago"), \
163163
.max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE, \
164164
}
165165

166+
static void gc_config_release(struct gc_config *cfg)
167+
{
168+
free(cfg->gc_log_expire);
169+
free(cfg->prune_expire);
170+
free(cfg->prune_worktrees_expire);
171+
free(cfg->repack_filter);
172+
free(cfg->repack_filter_to);
173+
}
174+
166175
static void gc_config(struct gc_config *cfg)
167176
{
168177
const char *value;
178+
char *owned = NULL;
169179

170180
if (!git_config_get_value("gc.packrefs", &value)) {
171181
if (value && !strcmp(value, "notbare"))
@@ -185,15 +195,34 @@ static void gc_config(struct gc_config *cfg)
185195
git_config_get_bool("gc.autodetach", &cfg->detach_auto);
186196
git_config_get_bool("gc.cruftpacks", &cfg->cruft_packs);
187197
git_config_get_ulong("gc.maxcruftsize", &cfg->max_cruft_size);
188-
git_config_get_expiry("gc.pruneexpire", (char **) &cfg->prune_expire);
189-
git_config_get_expiry("gc.worktreepruneexpire", (char **) &cfg->prune_worktrees_expire);
190-
git_config_get_expiry("gc.logexpiry", (char **) &cfg->gc_log_expire);
198+
199+
if (!git_config_get_expiry("gc.pruneexpire", &owned)) {
200+
free(cfg->prune_expire);
201+
cfg->prune_expire = owned;
202+
}
203+
204+
if (!git_config_get_expiry("gc.worktreepruneexpire", &owned)) {
205+
free(cfg->prune_worktrees_expire);
206+
cfg->prune_worktrees_expire = owned;
207+
}
208+
209+
if (!git_config_get_expiry("gc.logexpiry", &owned)) {
210+
free(cfg->gc_log_expire);
211+
cfg->gc_log_expire = owned;
212+
}
191213

192214
git_config_get_ulong("gc.bigpackthreshold", &cfg->big_pack_threshold);
193215
git_config_get_ulong("pack.deltacachesize", &cfg->max_delta_cache_size);
194216

195-
git_config_get_string("gc.repackfilter", &cfg->repack_filter);
196-
git_config_get_string("gc.repackfilterto", &cfg->repack_filter_to);
217+
if (!git_config_get_string("gc.repackfilter", &owned)) {
218+
free(cfg->repack_filter);
219+
cfg->repack_filter = owned;
220+
}
221+
222+
if (!git_config_get_string("gc.repackfilterto", &owned)) {
223+
free(cfg->repack_filter_to);
224+
cfg->repack_filter_to = owned;
225+
}
197226

198227
git_config(git_default_config, NULL);
199228
}
@@ -644,12 +673,15 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
644673
struct child_process rerere_cmd = CHILD_PROCESS_INIT;
645674
struct maintenance_run_opts opts = {0};
646675
struct gc_config cfg = GC_CONFIG_INIT;
676+
const char *prune_expire_sentinel = "sentinel";
677+
const char *prune_expire_arg = prune_expire_sentinel;
678+
int ret;
647679

648680
struct option builtin_gc_options[] = {
649681
OPT__QUIET(&quiet, N_("suppress progress reporting")),
650-
{ OPTION_STRING, 0, "prune", &cfg.prune_expire, N_("date"),
682+
{ OPTION_STRING, 0, "prune", &prune_expire_arg, N_("date"),
651683
N_("prune unreferenced objects"),
652-
PARSE_OPT_OPTARG, NULL, (intptr_t)cfg.prune_expire },
684+
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire_arg },
653685
OPT_BOOL(0, "cruft", &cfg.cruft_packs, N_("pack unreferenced objects separately")),
654686
OPT_MAGNITUDE(0, "max-cruft-size", &cfg.max_cruft_size,
655687
N_("with --cruft, limit the size of new cruft packs")),
@@ -673,8 +705,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
673705
strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
674706
strvec_pushl(&rerere, "rerere", "gc", NULL);
675707

676-
/* default expiry time, overwritten in gc_config */
677708
gc_config(&cfg);
709+
678710
if (parse_expiry_date(cfg.gc_log_expire, &gc_log_expire_time))
679711
die(_("failed to parse gc.logExpiry value %s"), cfg.gc_log_expire);
680712

@@ -686,6 +718,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
686718
if (argc > 0)
687719
usage_with_options(builtin_gc_usage, builtin_gc_options);
688720

721+
if (prune_expire_arg != prune_expire_sentinel) {
722+
free(cfg.prune_expire);
723+
cfg.prune_expire = xstrdup_or_null(prune_expire_arg);
724+
}
689725
if (cfg.prune_expire && parse_expiry_date(cfg.prune_expire, &dummy))
690726
die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
691727

@@ -703,8 +739,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
703739
/*
704740
* Auto-gc should be least intrusive as possible.
705741
*/
706-
if (!need_to_gc(&cfg))
707-
return 0;
742+
if (!need_to_gc(&cfg)) {
743+
ret = 0;
744+
goto out;
745+
}
746+
708747
if (!quiet) {
709748
if (cfg.detach_auto)
710749
fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
@@ -713,17 +752,22 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
713752
fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
714753
}
715754
if (cfg.detach_auto) {
716-
int ret = report_last_gc_error();
717-
718-
if (ret == 1)
755+
ret = report_last_gc_error();
756+
if (ret == 1) {
719757
/* Last gc --auto failed. Skip this one. */
720-
return 0;
721-
else if (ret)
758+
ret = 0;
759+
goto out;
760+
761+
} else if (ret) {
722762
/* an I/O error occurred, already reported */
723-
return ret;
763+
goto out;
764+
}
765+
766+
if (lock_repo_for_gc(force, &pid)) {
767+
ret = 0;
768+
goto out;
769+
}
724770

725-
if (lock_repo_for_gc(force, &pid))
726-
return 0;
727771
gc_before_repack(&opts, &cfg); /* dies on failure */
728772
delete_tempfile(&pidfile);
729773

@@ -749,8 +793,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
749793

750794
name = lock_repo_for_gc(force, &pid);
751795
if (name) {
752-
if (opts.auto_flag)
753-
return 0; /* be quiet on --auto */
796+
if (opts.auto_flag) {
797+
ret = 0;
798+
goto out; /* be quiet on --auto */
799+
}
800+
754801
die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
755802
name, (uintmax_t)pid);
756803
}
@@ -826,6 +873,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
826873
if (!daemonized)
827874
unlink(git_path("gc.log"));
828875

876+
out:
877+
gc_config_release(&cfg);
829878
return 0;
830879
}
831880

@@ -1511,6 +1560,8 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)
15111560
PARSE_OPT_NONEG, task_option_parse),
15121561
OPT_END()
15131562
};
1563+
int ret;
1564+
15141565
memset(&opts, 0, sizeof(opts));
15151566

15161567
opts.quiet = !isatty(2);
@@ -1532,7 +1583,10 @@ static int maintenance_run(int argc, const char **argv, const char *prefix)
15321583
if (argc != 0)
15331584
usage_with_options(builtin_maintenance_run_usage,
15341585
builtin_maintenance_run_options);
1535-
return maintenance_run_tasks(&opts, &cfg);
1586+
1587+
ret = maintenance_run_tasks(&opts, &cfg);
1588+
gc_config_release(&cfg);
1589+
return ret;
15361590
}
15371591

15381592
static char *get_maintpath(void)

t/t5304-prune.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ test_description='prune'
77
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
88
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
99

10+
TEST_PASSES_SANITIZE_LEAK=true
1011
. ./test-lib.sh
1112

1213
day=$((60*60*24))

0 commit comments

Comments
 (0)