From d822685c53b1407ecacfe5e5eb46b91ab274dbc0 Mon Sep 17 00:00:00 2001
From: Jochen Sprickerhof <git@jochen.sprickerhof.de>
Date: Fri, 21 Feb 2025 13:52:44 +0100
Subject: [PATCH] config/File: support resetting repeatable params

This allows resetting bind_to_address to override the default value in a
included config.
---
 doc/user.rst        |  4 ++++
 src/config/File.cxx | 21 +++++++++++++++------
 2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/doc/user.rst b/doc/user.rst
index ee77c0008..3249c8cb0 100644
--- a/doc/user.rst
+++ b/doc/user.rst
@@ -795,6 +795,10 @@ brackets if you want to configure a port::
 
  bind_to_address "[::1]:6602"
 
+To reset the previous assignments just set an empty value:
+
+ bind_to_address
+
 To bind to a local socket (UNIX domain socket), specify an absolute
 path or a path starting with a tilde (~).  Some clients default to
 connecting to :file:`/run/mpd/socket` so this may be a good
diff --git a/src/config/File.cxx b/src/config/File.cxx
index d3fb6846f..bb9b0c72c 100644
--- a/src/config/File.cxx
+++ b/src/config/File.cxx
@@ -30,10 +30,10 @@ static constexpr Domain config_file_domain("config_file");
  * Read a string value as the last token of a line.  Throws on error.
  */
 static auto
-ExpectValueAndEnd(Tokenizer &tokenizer)
+ExpectValueAndEnd(Tokenizer &tokenizer, bool repeatable)
 {
 	auto value = tokenizer.NextString();
-	if (!value)
+	if (!repeatable && !value)
 		throw std::runtime_error("Value missing");
 
 	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT)
@@ -50,7 +50,7 @@ config_read_name_value(ConfigBlock &block, char *input, unsigned line)
 	const char *name = tokenizer.NextWord();
 	assert(name != nullptr);
 
-	auto value = ExpectValueAndEnd(tokenizer);
+	auto value = ExpectValueAndEnd(tokenizer, false);
 
 	const BlockParam *bp = block.GetBlockParam(name);
 	if (bp != nullptr)
@@ -137,14 +137,23 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
 			   "config parameter {:?} on line {} is deprecated",
 			   name, reader.GetLineNumber());
 
+	auto value = ExpectValueAndEnd(tokenizer, option.repeatable);
+
 	if (!option.repeatable)
 		/* if the option is not repeatable, override the old
 		   value by removing it first */
 		config_data.GetParamList(o).clear();
+	else if(!value)
+	{
+		/* if it is a repeatable param and the value is empty
+		   clear the old values to allow resetting it */
+		config_data.GetParamList(o).clear();
+		return;
+	}
 
 	/* now parse the block or the value */
 
-	config_data.AddParam(o, ConfigParam(ExpectValueAndEnd(tokenizer),
+	config_data.AddParam(o, ConfigParam(value,
 					    reader.GetLineNumber()));
 }
 
@@ -174,7 +183,7 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Path directory)
 			// TODO: detect recursion
 			// TODO: Config{Block,Param} have only line number but no file name
 			const auto pattern = AllocatedPath::Apply(directory,
-								  AllocatedPath::FromUTF8Throw(ExpectValueAndEnd(tokenizer)));
+								  AllocatedPath::FromUTF8Throw(ExpectValueAndEnd(tokenizer, false)));
 			for (const auto &path : ListWildcard(pattern))
 				ReadConfigFile(config_data, path);
 			continue;
@@ -182,7 +191,7 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Path directory)
 
 		if (StringIsEqual(name, "include_optional")) {
 			const auto pattern = AllocatedPath::Apply(directory,
-								  AllocatedPath::FromUTF8Throw(ExpectValueAndEnd(tokenizer)));
+								  AllocatedPath::FromUTF8Throw(ExpectValueAndEnd(tokenizer, false)));
 
 			std::forward_list<AllocatedPath> l;
 			try {