config API: add "string" version of *_value_multi(), fix segfaults
Fix numerous and mostly long-standing segfaults in consumers of the *_config_*value_multi() API. As discussed in the preceding commit an empty key in the config syntax yields a "NULL" string, which these users would give to strcmp() (or similar), resulting in segfaults. As this change shows, most users users of the *_config_*value_multi() API didn't really want such an an unsafe and low-level API, let's give them something with the safety of git_config_get_string() instead. This fix is similar to what the *_string() functions and others acquired in[1] and [2]. Namely introducing and using a safer "*_get_string_multi()" variant of the low-level "_*value_multi()" function. This fixes segfaults in code introduced in: -d811c8e17c
(versionsort: support reorder prerelease suffixes, 2015-02-26) -c026557a37
(versioncmp: generalize version sort suffix reordering, 2016-12-08) -a086f921a7
(submodule: decouple url and submodule interest, 2017-03-17) -a6be5e6764
(log: add log.excludeDecoration config option, 2020-04-16) -92156291ca
(log: add default decoration filter, 2022-08-05) -50a044f1e4
(gc: replace config subprocesses with API calls, 2022-09-27) There are now two users ofthe low-level API: - One in "builtin/for-each-repo.c", which we'll convert in a subsequent commit. - The "t/helper/test-config.c" code added in [3]. As seen in the preceding commit we need to give the "t/helper/test-config.c" caller these "NULL" entries. We could also alter the underlying git_configset_get_value_multi() function to be "string safe", but doing so would leave no room for other variants of "*_get_value_multi()" that coerce to other types. Such coercion can't be built on the string version, since as we've established "NULL" is a true value in the boolean context, but if we coerced it to "" for use in a list of strings it'll be subsequently coerced to "false" as a boolean. The callback pattern being used here will make it easy to introduce e.g. a "multi" variant which coerces its values to "bool", "int", "path" etc. 1.40ea4ed903
(Add config_error_nonbool() helper function, 2008-02-11) 2.6c47d0e8f3
(config.c: guard config parser from value=NULL, 2008-02-11). 3.4c715ebb96
(test-config: add tests for the config_set API, 2014-07-28) Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
1c7e239bd0
commit
9e2d884d0f
@ -1510,7 +1510,7 @@ static int maintenance_register(int argc, const char **argv, const char *prefix)
|
|||||||
if (git_config_get("maintenance.strategy"))
|
if (git_config_get("maintenance.strategy"))
|
||||||
git_config_set("maintenance.strategy", "incremental");
|
git_config_set("maintenance.strategy", "incremental");
|
||||||
|
|
||||||
if (!git_config_get_value_multi(key, &list)) {
|
if (!git_config_get_string_multi(key, &list)) {
|
||||||
for_each_string_list_item(item, list) {
|
for_each_string_list_item(item, list) {
|
||||||
if (!strcmp(maintpath, item->string)) {
|
if (!strcmp(maintpath, item->string)) {
|
||||||
found = 1;
|
found = 1;
|
||||||
@ -1578,8 +1578,8 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
|
|||||||
git_configset_add_file(&cs, config_file);
|
git_configset_add_file(&cs, config_file);
|
||||||
}
|
}
|
||||||
if (!(config_file
|
if (!(config_file
|
||||||
? git_configset_get_value_multi(&cs, key, &list)
|
? git_configset_get_string_multi(&cs, key, &list)
|
||||||
: git_config_get_value_multi(key, &list))) {
|
: git_config_get_string_multi(key, &list))) {
|
||||||
for_each_string_list_item(item, list) {
|
for_each_string_list_item(item, list) {
|
||||||
if (!strcmp(maintpath, item->string)) {
|
if (!strcmp(maintpath, item->string)) {
|
||||||
found = 1;
|
found = 1;
|
||||||
|
@ -184,8 +184,8 @@ static void set_default_decoration_filter(struct decoration_filter *decoration_f
|
|||||||
struct string_list *include = decoration_filter->include_ref_pattern;
|
struct string_list *include = decoration_filter->include_ref_pattern;
|
||||||
const struct string_list *config_exclude;
|
const struct string_list *config_exclude;
|
||||||
|
|
||||||
if (!git_config_get_value_multi("log.excludeDecoration",
|
if (!git_config_get_string_multi("log.excludeDecoration",
|
||||||
&config_exclude)) {
|
&config_exclude)) {
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
for_each_string_list_item(item, config_exclude)
|
for_each_string_list_item(item, config_exclude)
|
||||||
string_list_append(decoration_filter->exclude_ref_config_pattern,
|
string_list_append(decoration_filter->exclude_ref_config_pattern,
|
||||||
|
32
config.c
32
config.c
@ -2434,6 +2434,25 @@ int git_configset_get_value_multi(struct config_set *cs, const char *key,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int check_multi_string(struct string_list_item *item, void *util)
|
||||||
|
{
|
||||||
|
return item->string ? 0 : config_error_nonbool(util);
|
||||||
|
}
|
||||||
|
|
||||||
|
int git_configset_get_string_multi(struct config_set *cs, const char *key,
|
||||||
|
const struct string_list **dest)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if ((ret = git_configset_get_value_multi(cs, key, dest)))
|
||||||
|
return ret;
|
||||||
|
if ((ret = for_each_string_list((struct string_list *)*dest,
|
||||||
|
check_multi_string, (void *)key)))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int git_configset_get(struct config_set *cs, const char *key)
|
int git_configset_get(struct config_set *cs, const char *key)
|
||||||
{
|
{
|
||||||
struct config_set_element *e;
|
struct config_set_element *e;
|
||||||
@ -2602,6 +2621,13 @@ int repo_config_get_value_multi(struct repository *repo, const char *key,
|
|||||||
return git_configset_get_value_multi(repo->config, key, dest);
|
return git_configset_get_value_multi(repo->config, key, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int repo_config_get_string_multi(struct repository *repo, const char *key,
|
||||||
|
const struct string_list **dest)
|
||||||
|
{
|
||||||
|
git_config_check_init(repo);
|
||||||
|
return git_configset_get_string_multi(repo->config, key, dest);
|
||||||
|
}
|
||||||
|
|
||||||
int repo_config_get_string(struct repository *repo,
|
int repo_config_get_string(struct repository *repo,
|
||||||
const char *key, char **dest)
|
const char *key, char **dest)
|
||||||
{
|
{
|
||||||
@ -2717,6 +2743,12 @@ int git_config_get_value_multi(const char *key, const struct string_list **dest)
|
|||||||
return repo_config_get_value_multi(the_repository, key, dest);
|
return repo_config_get_value_multi(the_repository, key, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int git_config_get_string_multi(const char *key,
|
||||||
|
const struct string_list **dest)
|
||||||
|
{
|
||||||
|
return repo_config_get_string_multi(the_repository, key, dest);
|
||||||
|
}
|
||||||
|
|
||||||
int git_config_get_string(const char *key, char **dest)
|
int git_config_get_string(const char *key, char **dest)
|
||||||
{
|
{
|
||||||
return repo_config_get_string(the_repository, key, dest);
|
return repo_config_get_string(the_repository, key, dest);
|
||||||
|
19
config.h
19
config.h
@ -472,6 +472,19 @@ RESULT_MUST_BE_USED
|
|||||||
int git_configset_get_value_multi(struct config_set *cs, const char *key,
|
int git_configset_get_value_multi(struct config_set *cs, const char *key,
|
||||||
const struct string_list **dest);
|
const struct string_list **dest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A validation wrapper for git_configset_get_value_multi() which does
|
||||||
|
* for it what git_configset_get_string() does for
|
||||||
|
* git_configset_get_value().
|
||||||
|
*
|
||||||
|
* The configuration syntax allows for "[section] key", which will
|
||||||
|
* give us a NULL entry in the "struct string_list", as opposed to
|
||||||
|
* "[section] key =" which is the empty string. Most users of the API
|
||||||
|
* are not prepared to handle NULL in a "struct string_list".
|
||||||
|
*/
|
||||||
|
int git_configset_get_string_multi(struct config_set *cs, const char *key,
|
||||||
|
const struct string_list **dest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears `config_set` structure, removes all saved variable-value pairs.
|
* Clears `config_set` structure, removes all saved variable-value pairs.
|
||||||
*/
|
*/
|
||||||
@ -522,6 +535,9 @@ int repo_config_get_value(struct repository *repo,
|
|||||||
RESULT_MUST_BE_USED
|
RESULT_MUST_BE_USED
|
||||||
int repo_config_get_value_multi(struct repository *repo, const char *key,
|
int repo_config_get_value_multi(struct repository *repo, const char *key,
|
||||||
const struct string_list **dest);
|
const struct string_list **dest);
|
||||||
|
RESULT_MUST_BE_USED
|
||||||
|
int repo_config_get_string_multi(struct repository *repo, const char *key,
|
||||||
|
const struct string_list **dest);
|
||||||
int repo_config_get_string(struct repository *repo,
|
int repo_config_get_string(struct repository *repo,
|
||||||
const char *key, char **dest);
|
const char *key, char **dest);
|
||||||
int repo_config_get_string_tmp(struct repository *repo,
|
int repo_config_get_string_tmp(struct repository *repo,
|
||||||
@ -583,6 +599,9 @@ int git_config_get_value(const char *key, const char **value);
|
|||||||
RESULT_MUST_BE_USED
|
RESULT_MUST_BE_USED
|
||||||
int git_config_get_value_multi(const char *key,
|
int git_config_get_value_multi(const char *key,
|
||||||
const struct string_list **dest);
|
const struct string_list **dest);
|
||||||
|
RESULT_MUST_BE_USED
|
||||||
|
int git_config_get_string_multi(const char *key,
|
||||||
|
const struct string_list **dest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets and invalidates the config cache.
|
* Resets and invalidates the config cache.
|
||||||
|
@ -2303,7 +2303,7 @@ const struct string_list *bitmap_preferred_tips(struct repository *r)
|
|||||||
{
|
{
|
||||||
const struct string_list *dest;
|
const struct string_list *dest;
|
||||||
|
|
||||||
if (!repo_config_get_value_multi(r, "pack.preferbitmaptips", &dest))
|
if (!repo_config_get_string_multi(r, "pack.preferbitmaptips", &dest))
|
||||||
return dest;
|
return dest;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ int is_tree_submodule_active(struct repository *repo,
|
|||||||
free(key);
|
free(key);
|
||||||
|
|
||||||
/* submodule.active is set */
|
/* submodule.active is set */
|
||||||
if (!repo_config_get_value_multi(repo, "submodule.active", &sl)) {
|
if (!repo_config_get_string_multi(repo, "submodule.active", &sl)) {
|
||||||
struct pathspec ps;
|
struct pathspec ps;
|
||||||
struct strvec args = STRVEC_INIT;
|
struct strvec args = STRVEC_INIT;
|
||||||
const struct string_list_item *item;
|
const struct string_list_item *item;
|
||||||
|
@ -835,7 +835,7 @@ test_expect_success 'log.decorate configuration' '
|
|||||||
|
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'parse log.excludeDecoration with no value' '
|
test_expect_success 'parse log.excludeDecoration with no value' '
|
||||||
cp .git/config .git/config.orig &&
|
cp .git/config .git/config.orig &&
|
||||||
test_when_finished mv .git/config.orig .git/config &&
|
test_when_finished mv .git/config.orig .git/config &&
|
||||||
|
|
||||||
@ -843,7 +843,11 @@ test_expect_failure 'parse log.excludeDecoration with no value' '
|
|||||||
[log]
|
[log]
|
||||||
excludeDecoration
|
excludeDecoration
|
||||||
EOF
|
EOF
|
||||||
git log --decorate=short
|
cat >expect <<-\EOF &&
|
||||||
|
error: missing value for '\''log.excludeDecoration'\''
|
||||||
|
EOF
|
||||||
|
git log --decorate=short 2>actual &&
|
||||||
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'decorate-refs with glob' '
|
test_expect_success 'decorate-refs with glob' '
|
||||||
|
@ -404,7 +404,7 @@ test_bitmap_cases () {
|
|||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'pack.preferBitmapTips' '
|
test_expect_success 'pack.preferBitmapTips' '
|
||||||
git init repo &&
|
git init repo &&
|
||||||
test_when_finished "rm -rf repo" &&
|
test_when_finished "rm -rf repo" &&
|
||||||
(
|
(
|
||||||
@ -416,7 +416,11 @@ test_bitmap_cases () {
|
|||||||
[pack]
|
[pack]
|
||||||
preferBitmapTips
|
preferBitmapTips
|
||||||
EOF
|
EOF
|
||||||
git repack -adb
|
cat >expect <<-\EOF &&
|
||||||
|
error: missing value for '\''pack.preferbitmaptips'\''
|
||||||
|
EOF
|
||||||
|
git repack -adb 2>actual &&
|
||||||
|
test_cmp expect actual
|
||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
@ -1843,7 +1843,7 @@ test_expect_success 'invalid sort parameter in configuratoin' '
|
|||||||
test_must_fail git tag -l "foo*"
|
test_must_fail git tag -l "foo*"
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'version sort handles empty value for versionsort.{prereleaseSuffix,suffix}' '
|
test_expect_success 'version sort handles empty value for versionsort.{prereleaseSuffix,suffix}' '
|
||||||
cp .git/config .git/config.orig &&
|
cp .git/config .git/config.orig &&
|
||||||
test_when_finished mv .git/config.orig .git/config &&
|
test_when_finished mv .git/config.orig .git/config &&
|
||||||
|
|
||||||
@ -1852,7 +1852,12 @@ test_expect_failure 'version sort handles empty value for versionsort.{prereleas
|
|||||||
prereleaseSuffix
|
prereleaseSuffix
|
||||||
suffix
|
suffix
|
||||||
EOF
|
EOF
|
||||||
git tag -l --sort=version:refname
|
cat >expect <<-\EOF &&
|
||||||
|
error: missing value for '\''versionsort.suffix'\''
|
||||||
|
error: missing value for '\''versionsort.prereleasesuffix'\''
|
||||||
|
EOF
|
||||||
|
git tag -l --sort=version:refname 2>actual &&
|
||||||
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'version sort with prerelease reordering' '
|
test_expect_success 'version sort with prerelease reordering' '
|
||||||
|
@ -51,7 +51,7 @@ test_expect_success 'is-active works with submodule.<name>.active config' '
|
|||||||
test-tool -C super submodule is-active sub1
|
test-tool -C super submodule is-active sub1
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'is-active handles submodule.active config missing a value' '
|
test_expect_success 'is-active handles submodule.active config missing a value' '
|
||||||
cp super/.git/config super/.git/config.orig &&
|
cp super/.git/config super/.git/config.orig &&
|
||||||
test_when_finished mv super/.git/config.orig super/.git/config &&
|
test_when_finished mv super/.git/config.orig super/.git/config &&
|
||||||
|
|
||||||
@ -60,7 +60,11 @@ test_expect_failure 'is-active handles submodule.active config missing a value'
|
|||||||
active
|
active
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test-tool -C super submodule is-active sub1
|
cat >expect <<-\EOF &&
|
||||||
|
error: missing value for '\''submodule.active'\''
|
||||||
|
EOF
|
||||||
|
test-tool -C super submodule is-active sub1 2>actual &&
|
||||||
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'is-active works with basic submodule.active config' '
|
test_expect_success 'is-active works with basic submodule.active config' '
|
||||||
|
@ -524,7 +524,7 @@ test_expect_success 'register and unregister' '
|
|||||||
git maintenance unregister --config-file ./other --force
|
git maintenance unregister --config-file ./other --force
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'register with no value for maintenance.repo' '
|
test_expect_success 'register with no value for maintenance.repo' '
|
||||||
cp .git/config .git/config.orig &&
|
cp .git/config .git/config.orig &&
|
||||||
test_when_finished mv .git/config.orig .git/config &&
|
test_when_finished mv .git/config.orig .git/config &&
|
||||||
|
|
||||||
@ -532,10 +532,15 @@ test_expect_failure 'register with no value for maintenance.repo' '
|
|||||||
[maintenance]
|
[maintenance]
|
||||||
repo
|
repo
|
||||||
EOF
|
EOF
|
||||||
git maintenance register
|
cat >expect <<-\EOF &&
|
||||||
|
error: missing value for '\''maintenance.repo'\''
|
||||||
|
EOF
|
||||||
|
git maintenance register 2>actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git config maintenance.repo
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_failure 'unregister with no value for maintenance.repo' '
|
test_expect_success 'unregister with no value for maintenance.repo' '
|
||||||
cp .git/config .git/config.orig &&
|
cp .git/config .git/config.orig &&
|
||||||
test_when_finished mv .git/config.orig .git/config &&
|
test_when_finished mv .git/config.orig .git/config &&
|
||||||
|
|
||||||
@ -543,8 +548,18 @@ test_expect_failure 'unregister with no value for maintenance.repo' '
|
|||||||
[maintenance]
|
[maintenance]
|
||||||
repo
|
repo
|
||||||
EOF
|
EOF
|
||||||
git maintenance unregister &&
|
cat >expect <<-\EOF &&
|
||||||
git maintenance unregister --force
|
error: missing value for '\''maintenance.repo'\''
|
||||||
|
EOF
|
||||||
|
test_expect_code 128 git maintenance unregister 2>actual.raw &&
|
||||||
|
grep ^error actual.raw >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git config maintenance.repo &&
|
||||||
|
|
||||||
|
git maintenance unregister --force 2>actual.raw &&
|
||||||
|
grep ^error actual.raw >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git config maintenance.repo
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success !MINGW 'register and unregister with regex metacharacters' '
|
test_expect_success !MINGW 'register and unregister with regex metacharacters' '
|
||||||
|
@ -164,8 +164,8 @@ int versioncmp(const char *s1, const char *s2)
|
|||||||
const char *const oldk = "versionsort.prereleasesuffix";
|
const char *const oldk = "versionsort.prereleasesuffix";
|
||||||
const struct string_list *newl;
|
const struct string_list *newl;
|
||||||
const struct string_list *oldl;
|
const struct string_list *oldl;
|
||||||
int new = git_config_get_value_multi(newk, &newl);
|
int new = git_config_get_string_multi(newk, &newl);
|
||||||
int old = git_config_get_value_multi(oldk, &oldl);
|
int old = git_config_get_string_multi(oldk, &oldl);
|
||||||
|
|
||||||
if (!new && !old)
|
if (!new && !old)
|
||||||
warning("ignoring %s because %s is set", oldk, newk);
|
warning("ignoring %s because %s is set", oldk, newk);
|
||||||
|
Loading…
Reference in New Issue
Block a user