From 6edfc56c9df7b212c65a5a40e9d0f132429578ea Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 6 Dec 2014 00:08:08 +0100
Subject: [PATCH] command: use ConstBuffer<const char *> for argument list

---
 src/command/AllCommands.cxx      |  63 ++++++++---------
 src/command/DatabaseCommands.cxx |  62 +++++++----------
 src/command/DatabaseCommands.hxx |  21 +++---
 src/command/FileCommands.cxx     |   8 +--
 src/command/FileCommands.hxx     |   3 +-
 src/command/MessageCommands.cxx  |  36 +++++-----
 src/command/MessageCommands.hxx  |  11 +--
 src/command/NeighborCommands.cxx |   4 +-
 src/command/NeighborCommands.hxx |   3 +-
 src/command/OtherCommands.cxx    |  83 ++++++++++------------
 src/command/OtherCommands.hxx    |  33 ++++-----
 src/command/OutputCommands.cxx   |  24 ++++---
 src/command/OutputCommands.hxx   |   9 +--
 src/command/PlayerCommands.cxx   |  86 +++++++++++------------
 src/command/PlayerCommands.hxx   |  43 ++++++------
 src/command/PlaylistCommands.cxx |  78 ++++++++++++---------
 src/command/PlaylistCommands.hxx |  23 ++++---
 src/command/QueueCommands.cxx    | 114 +++++++++++++++----------------
 src/command/QueueCommands.hxx    |  41 +++++------
 src/command/StickerCommands.cxx  |  47 +++++++------
 src/command/StickerCommands.hxx  |   3 +-
 src/command/StorageCommands.cxx  |  13 ++--
 src/command/StorageCommands.hxx  |   7 +-
 src/command/TagCommands.cxx      |  17 ++---
 src/command/TagCommands.hxx      |   5 +-
 25 files changed, 417 insertions(+), 420 deletions(-)

diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index b2d9f52d3..e5519c4c3 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -37,6 +37,7 @@
 #include "client/Client.hxx"
 #include "util/Tokenizer.hxx"
 #include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
 
 #ifdef ENABLE_SQLITE
 #include "StickerCommands.hxx"
@@ -60,15 +61,15 @@ struct command {
 	unsigned permission;
 	int min;
 	int max;
-	CommandResult (*handler)(Client &client, unsigned argc, char **argv);
+	CommandResult (*handler)(Client &client, ConstBuffer<const char *> args);
 };
 
 /* don't be fooled, this is the command handler for "commands" command */
 static CommandResult
-handle_commands(Client &client, unsigned argc, char *argv[]);
+handle_commands(Client &client, ConstBuffer<const char *> args);
 
 static CommandResult
-handle_not_commands(Client &client, unsigned argc, char *argv[]);
+handle_not_commands(Client &client, ConstBuffer<const char *> args);
 
 /**
  * The command registry.
@@ -225,8 +226,7 @@ command_available(gcc_unused const Partition &partition,
 
 /* don't be fooled, this is the command handler for "commands" command */
 static CommandResult
-handle_commands(Client &client,
-		gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_commands(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	const unsigned permission = client.GetPermission();
 
@@ -242,8 +242,7 @@ handle_commands(Client &client,
 }
 
 static CommandResult
-handle_not_commands(Client &client,
-		    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_not_commands(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	const unsigned permission = client.GetPermission();
 
@@ -293,11 +292,8 @@ command_lookup(const char *name)
 
 static bool
 command_check_request(const struct command *cmd, Client &client,
-		      unsigned permission, unsigned argc, char *argv[])
+		      unsigned permission, ConstBuffer<const char *> args)
 {
-	const unsigned min = cmd->min + 1;
-	const unsigned max = cmd->max + 1;
-
 	if (cmd->permission != (permission & cmd->permission)) {
 		command_error(client, ACK_ERROR_PERMISSION,
 			      "you don't have permission for \"%s\"",
@@ -305,21 +301,24 @@ command_check_request(const struct command *cmd, Client &client,
 		return false;
 	}
 
-	if (min == 0)
+	const int min = cmd->min;
+	const int max = cmd->max;
+
+	if (min < 0)
 		return true;
 
-	if (min == max && max != argc) {
+	if (min == max && unsigned(max) != args.size) {
 		command_error(client, ACK_ERROR_ARG,
 			      "wrong number of arguments for \"%s\"",
-			      argv[0]);
+			      cmd->cmd);
 		return false;
-	} else if (argc < min) {
+	} else if (args.size < unsigned(min)) {
 		command_error(client, ACK_ERROR_ARG,
-			      "too few arguments for \"%s\"", argv[0]);
+			      "too few arguments for \"%s\"", cmd->cmd);
 		return false;
-	} else if (argc > max && max /* != 0 */ ) {
+	} else if (max >= 0 && args.size > unsigned(max)) {
 		command_error(client, ACK_ERROR_ARG,
-			      "too many arguments for \"%s\"", argv[0]);
+			      "too many arguments for \"%s\"", cmd->cmd);
 		return false;
 	} else
 		return true;
@@ -327,23 +326,20 @@ command_check_request(const struct command *cmd, Client &client,
 
 static const struct command *
 command_checked_lookup(Client &client, unsigned permission,
-		       unsigned argc, char *argv[])
+		       const char *cmd_name, ConstBuffer<const char *> args)
 {
 	current_command = "";
 
-	if (argc == 0)
-		return nullptr;
-
-	const struct command *cmd = command_lookup(argv[0]);
+	const struct command *cmd = command_lookup(cmd_name);
 	if (cmd == nullptr) {
 		command_error(client, ACK_ERROR_UNKNOWN,
-			      "unknown command \"%s\"", argv[0]);
+			      "unknown command \"%s\"", cmd_name);
 		return nullptr;
 	}
 
 	current_command = cmd->cmd;
 
-	if (!command_check_request(cmd, client, permission, argc, argv))
+	if (!command_check_request(cmd, client, permission, args))
 		return nullptr;
 
 	return cmd;
@@ -362,9 +358,9 @@ command_process(Client &client, unsigned num, char *line)
 
 	Tokenizer tokenizer(line);
 
-	char *argv[COMMAND_ARGV_MAX];
-	current_command = argv[0] = tokenizer.NextWord(error);
-	if (argv[0] == nullptr) {
+	const char *const cmd_name = current_command =
+		tokenizer.NextWord(error);
+	if (cmd_name == nullptr) {
 		current_command = "";
 		if (tokenizer.IsEnd())
 			command_error(client, ACK_ERROR_UNKNOWN,
@@ -380,12 +376,13 @@ command_process(Client &client, unsigned num, char *line)
 		return CommandResult::FINISH;
 	}
 
-	unsigned argc = 1;
+	char *argv[COMMAND_ARGV_MAX];
+	ConstBuffer<const char *> args(argv, 0);
 
 	/* now parse the arguments (quoted or unquoted) */
 
 	while (true) {
-		if (argc == COMMAND_ARGV_MAX) {
+		if (args.size == COMMAND_ARGV_MAX) {
 			command_error(client, ACK_ERROR_ARG,
 				      "Too many arguments");
 			current_command = nullptr;
@@ -402,17 +399,17 @@ command_process(Client &client, unsigned num, char *line)
 			return CommandResult::ERROR;
 		}
 
-		argv[argc++] = a;
+		argv[args.size++] = a;
 	}
 
 	/* look up and invoke the command handler */
 
 	const struct command *cmd =
 		command_checked_lookup(client, client.GetPermission(),
-				       argc, argv);
+				       cmd_name, args);
 
 	CommandResult ret = cmd
-		? cmd->handler(client, argc, argv)
+		? cmd->handler(client, args)
 		: CommandResult::ERROR;
 
 	current_command = nullptr;
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index 988542f44..2d1e1f69b 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -50,12 +50,10 @@ handle_listfiles_db(Client &client, const char *uri)
 }
 
 CommandResult
-handle_lsinfo2(Client &client, unsigned argc, char *argv[])
+handle_lsinfo2(Client &client, ConstBuffer<const char *> args)
 {
-	const char *const uri = argc == 2
-		? argv[1]
-		/* default is root directory */
-		: "";
+	/* default is root directory */
+	const char *const uri = args.IsEmpty() ? "" : args.front();
 
 	const DatabaseSelection selection(uri, false);
 
@@ -67,10 +65,8 @@ handle_lsinfo2(Client &client, unsigned argc, char *argv[])
 }
 
 static CommandResult
-handle_match(Client &client, unsigned argc, char *argv[], bool fold_case)
+handle_match(Client &client, ConstBuffer<const char *> args, bool fold_case)
 {
-	ConstBuffer<const char *> args(argv + 1, argc - 1);
-
 	unsigned window_start = 0, window_end = std::numeric_limits<int>::max();
 	if (args.size >= 2 && strcmp(args[args.size - 2], "window") == 0) {
 		if (!check_range(client, &window_start, &window_end,
@@ -97,22 +93,20 @@ handle_match(Client &client, unsigned argc, char *argv[], bool fold_case)
 }
 
 CommandResult
-handle_find(Client &client, unsigned argc, char *argv[])
+handle_find(Client &client, ConstBuffer<const char *> args)
 {
-	return handle_match(client, argc, argv, false);
+	return handle_match(client, args, false);
 }
 
 CommandResult
-handle_search(Client &client, unsigned argc, char *argv[])
+handle_search(Client &client, ConstBuffer<const char *> args)
 {
-	return handle_match(client, argc, argv, true);
+	return handle_match(client, args, true);
 }
 
 static CommandResult
-handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case)
+handle_match_add(Client &client, ConstBuffer<const char *> args, bool fold_case)
 {
-	ConstBuffer<const char *> args(argv + 1, argc - 1);
-
 	SongFilter filter;
 	if (!filter.Parse(args, fold_case)) {
 		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
@@ -129,21 +123,20 @@ handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case)
 }
 
 CommandResult
-handle_findadd(Client &client, unsigned argc, char *argv[])
+handle_findadd(Client &client, ConstBuffer<const char *> args)
 {
-	return handle_match_add(client, argc, argv, false);
+	return handle_match_add(client, args, false);
 }
 
 CommandResult
-handle_searchadd(Client &client, unsigned argc, char *argv[])
+handle_searchadd(Client &client, ConstBuffer<const char *> args)
 {
-	return handle_match_add(client, argc, argv, true);
+	return handle_match_add(client, args, true);
 }
 
 CommandResult
-handle_searchaddpl(Client &client, unsigned argc, char *argv[])
+handle_searchaddpl(Client &client, ConstBuffer<const char *> args)
 {
-	ConstBuffer<const char *> args(argv + 1, argc - 1);
 	const char *playlist = args.shift();
 
 	SongFilter filter;
@@ -164,10 +157,8 @@ handle_searchaddpl(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_count(Client &client, unsigned argc, char *argv[])
+handle_count(Client &client, ConstBuffer<const char *> args)
 {
-	ConstBuffer<const char *> args(argv + 1, argc - 1);
-
 	TagType group = TAG_NUM_OF_ITEM_TYPES;
 	if (args.size >= 2 && strcmp(args[args.size - 2], "group") == 0) {
 		const char *s = args[args.size - 1];
@@ -195,24 +186,21 @@ handle_count(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_listall(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_listall(Client &client, ConstBuffer<const char *> args)
 {
-	const char *directory = "";
-
-	if (argc == 2)
-		directory = argv[1];
+	/* default is root directory */
+	const char *const uri = args.IsEmpty() ? "" : args.front();
 
 	Error error;
-	return db_selection_print(client, DatabaseSelection(directory, true),
+	return db_selection_print(client, DatabaseSelection(uri, true),
 				  false, false, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_list(Client &client, unsigned argc, char *argv[])
+handle_list(Client &client, ConstBuffer<const char *> args)
 {
-	ConstBuffer<const char *> args(argv + 1, argc - 1);
 	const char *tag_name = args.shift();
 	unsigned tagType = locate_parse_type(tag_name);
 
@@ -283,15 +271,13 @@ handle_list(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_listallinfo(Client &client, ConstBuffer<const char *> args)
 {
-	const char *directory = "";
-
-	if (argc == 2)
-		directory = argv[1];
+	/* default is root directory */
+	const char *const uri = args.IsEmpty() ? "" : args.front();
 
 	Error error;
-	return db_selection_print(client, DatabaseSelection(directory, true),
+	return db_selection_print(client, DatabaseSelection(uri, true),
 				  true, false, error)
 		? CommandResult::OK
 		: print_error(client, error);
diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx
index 7abf89e0c..0f6e2700a 100644
--- a/src/command/DatabaseCommands.hxx
+++ b/src/command/DatabaseCommands.hxx
@@ -23,38 +23,39 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
 handle_listfiles_db(Client &client, const char *uri);
 
 CommandResult
-handle_lsinfo2(Client &client, unsigned argc, char *argv[]);
+handle_lsinfo2(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_find(Client &client, unsigned argc, char *argv[]);
+handle_find(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_findadd(Client &client, unsigned argc, char *argv[]);
+handle_findadd(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_search(Client &client, unsigned argc, char *argv[]);
+handle_search(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_searchadd(Client &client, unsigned argc, char *argv[]);
+handle_searchadd(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_searchaddpl(Client &client, unsigned argc, char *argv[]);
+handle_searchaddpl(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_count(Client &client, unsigned argc, char *argv[]);
+handle_count(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_listall(Client &client, unsigned argc, char *argv[]);
+handle_listall(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_list(Client &client, unsigned argc, char *argv[]);
+handle_list(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_listallinfo(Client &client, unsigned argc, char *argv[]);
+handle_listallinfo(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx
index 1b6a11cf5..acf71eca4 100644
--- a/src/command/FileCommands.cxx
+++ b/src/command/FileCommands.cxx
@@ -25,6 +25,7 @@
 #include "protocol/Ack.hxx"
 #include "protocol/Result.hxx"
 #include "client/Client.hxx"
+#include "util/ConstBuffer.hxx"
 #include "util/CharUtil.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
@@ -202,11 +203,10 @@ read_file_comments(Client &client, const Path path_fs)
 }
 
 CommandResult
-handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_read_comments(Client &client, ConstBuffer<const char *> args)
 {
-	assert(argc == 2);
-
-	const char *const uri = argv[1];
+	assert(args.size == 1);
+	const char *const uri = args.front();
 
 	if (memcmp(uri, "file:///", 8) == 0) {
 		/* read comments from arbitrary local file */
diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx
index 62835a82c..b77157e3f 100644
--- a/src/command/FileCommands.hxx
+++ b/src/command/FileCommands.hxx
@@ -23,11 +23,12 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
 handle_listfiles_local(Client &client, const char *path_utf8);
 
 CommandResult
-handle_read_comments(Client &client, unsigned argc, char *argv[]);
+handle_read_comments(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx
index a86bdf30c..4bf22abcc 100644
--- a/src/command/MessageCommands.cxx
+++ b/src/command/MessageCommands.cxx
@@ -24,6 +24,7 @@
 #include "Instance.hxx"
 #include "Partition.hxx"
 #include "protocol/Result.hxx"
+#include "util/ConstBuffer.hxx"
 
 #include <set>
 #include <string>
@@ -31,11 +32,12 @@
 #include <assert.h>
 
 CommandResult
-handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_subscribe(Client &client, ConstBuffer<const char *> args)
 {
-	assert(argc == 2);
+	assert(args.size == 1);
+	const char *const channel_name = args[0];
 
-	switch (client.Subscribe(argv[1])) {
+	switch (client.Subscribe(channel_name)) {
 	case Client::SubscribeResult::OK:
 		return CommandResult::OK;
 
@@ -61,11 +63,12 @@ handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_unsubscribe(Client &client, ConstBuffer<const char *> args)
 {
-	assert(argc == 2);
+	assert(args.size == 1);
+	const char *const channel_name = args[0];
 
-	if (client.Unsubscribe(argv[1]))
+	if (client.Unsubscribe(channel_name))
 		return CommandResult::OK;
 	else {
 		command_error(client, ACK_ERROR_NO_EXIST,
@@ -75,10 +78,9 @@ handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_channels(Client &client,
-		gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_channels(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
-	assert(argc == 1);
+	assert(args.IsEmpty());
 
 	std::set<std::string> channels;
 	for (const auto &c : *client.partition.instance.client_list)
@@ -93,9 +95,9 @@ handle_channels(Client &client,
 
 CommandResult
 handle_read_messages(Client &client,
-		     gcc_unused unsigned argc, gcc_unused char *argv[])
+		     gcc_unused ConstBuffer<const char *> args)
 {
-	assert(argc == 1);
+	assert(args.IsEmpty());
 
 	while (!client.messages.empty()) {
 		const ClientMessage &msg = client.messages.front();
@@ -109,19 +111,21 @@ handle_read_messages(Client &client,
 }
 
 CommandResult
-handle_send_message(Client &client,
-		    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_send_message(Client &client, ConstBuffer<const char *> args)
 {
-	assert(argc == 3);
+	assert(args.size == 2);
 
-	if (!client_message_valid_channel_name(argv[1])) {
+	const char *const channel_name = args[0];
+	const char *const message_text = args[1];
+
+	if (!client_message_valid_channel_name(channel_name)) {
 		command_error(client, ACK_ERROR_ARG,
 			      "invalid channel name");
 		return CommandResult::ERROR;
 	}
 
 	bool sent = false;
-	const ClientMessage msg(argv[1], argv[2]);
+	const ClientMessage msg(channel_name, message_text);
 	for (auto &c : *client.partition.instance.client_list)
 		if (c.PushMessage(msg))
 			sent = true;
diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx
index ac8afe2fb..b10863277 100644
--- a/src/command/MessageCommands.hxx
+++ b/src/command/MessageCommands.hxx
@@ -23,20 +23,21 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_subscribe(Client &client, unsigned argc, char *argv[]);
+handle_subscribe(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_unsubscribe(Client &client, unsigned argc, char *argv[]);
+handle_unsubscribe(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_channels(Client &client, unsigned argc, char *argv[]);
+handle_channels(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_read_messages(Client &client, unsigned argc, char *argv[]);
+handle_read_messages(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_send_message(Client &client, unsigned argc, char *argv[]);
+handle_send_message(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx
index 22e8adf9e..3efae7883 100644
--- a/src/command/NeighborCommands.cxx
+++ b/src/command/NeighborCommands.cxx
@@ -25,6 +25,7 @@
 #include "protocol/Result.hxx"
 #include "neighbor/Glue.hxx"
 #include "neighbor/Info.hxx"
+#include "util/ConstBuffer.hxx"
 
 #include <set>
 #include <string>
@@ -38,8 +39,7 @@ neighbor_commands_available(const Instance &instance)
 }
 
 CommandResult
-handle_listneighbors(Client &client,
-		     gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_listneighbors(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	const NeighborGlue *const neighbors =
 		client.partition.instance.neighbors;
diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx
index 7fb309aeb..e07f26925 100644
--- a/src/command/NeighborCommands.hxx
+++ b/src/command/NeighborCommands.hxx
@@ -25,12 +25,13 @@
 
 struct Instance;
 class Client;
+template<typename T> struct ConstBuffer;
 
 gcc_pure
 bool
 neighbor_commands_available(const Instance &instance);
 
 CommandResult
-handle_listneighbors(Client &client, unsigned argc, char *argv[]);
+handle_listneighbors(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx
index a924f77b5..6328acc4c 100644
--- a/src/command/OtherCommands.cxx
+++ b/src/command/OtherCommands.cxx
@@ -37,6 +37,7 @@
 #include "mixer/Volume.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
 #include "fs/AllocatedPath.hxx"
 #include "Stats.hxx"
 #include "Permission.hxx"
@@ -68,8 +69,7 @@ print_spl_list(Client &client, const PlaylistVector &list)
 }
 
 CommandResult
-handle_urlhandlers(Client &client,
-		   gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_urlhandlers(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	if (client.IsLocal())
 		client_puts(client, "handler: file://\n");
@@ -78,31 +78,27 @@ handle_urlhandlers(Client &client,
 }
 
 CommandResult
-handle_decoders(Client &client,
-		gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_decoders(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	decoder_list_print(client);
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_tagtypes(Client &client,
-		gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_tagtypes(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	tag_print_types(client);
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_kill(gcc_unused Client &client,
-	    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_kill(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	return CommandResult::KILL;
 }
 
 CommandResult
-handle_close(gcc_unused Client &client,
-	     gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_close(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	return CommandResult::FINISH;
 }
@@ -116,12 +112,10 @@ print_tag(TagType type, const char *value, void *ctx)
 }
 
 CommandResult
-handle_listfiles(Client &client, unsigned argc, char *argv[])
+handle_listfiles(Client &client, ConstBuffer<const char *> args)
 {
-	const char *const uri = argc == 2
-		? argv[1]
-		/* default is root directory */
-		: "";
+	/* default is root directory */
+	const char *const uri = args.IsEmpty() ? "" : args.front();
 
 	if (memcmp(uri, "file:///", 8) == 0)
 		/* list local directory */
@@ -157,12 +151,10 @@ static constexpr tag_handler print_tag_handler = {
 };
 
 CommandResult
-handle_lsinfo(Client &client, unsigned argc, char *argv[])
+handle_lsinfo(Client &client, ConstBuffer<const char *> args)
 {
-	const char *const uri = argc == 2
-		? argv[1]
-		/* default is root directory */
-		: "";
+	/* default is root directory */
+	const char *const uri = args.IsEmpty() ? "" : args.front();
 
 	if (memcmp(uri, "file:///", 8) == 0) {
 		/* print information about an arbitrary local file */
@@ -207,7 +199,7 @@ handle_lsinfo(Client &client, unsigned argc, char *argv[])
 	}
 
 #ifdef ENABLE_DATABASE
-	CommandResult result = handle_lsinfo2(client, argc, argv);
+	CommandResult result = handle_lsinfo2(client, args);
 	if (result != CommandResult::OK)
 		return result;
 #endif
@@ -265,14 +257,14 @@ handle_update(Client &client, Database &db,
 #endif
 
 static CommandResult
-handle_update(Client &client, unsigned argc, char *argv[], bool discard)
+handle_update(Client &client, ConstBuffer<const char *> args, bool discard)
 {
 #ifdef ENABLE_DATABASE
 	const char *path = "";
 
-	assert(argc <= 2);
-	if (argc == 2) {
-		path = argv[1];
+	assert(args.size <= 1);
+	if (!args.IsEmpty()) {
+		path = args.front();
 
 		if (*path == 0 || strcmp(path, "/") == 0)
 			/* backwards compatibility with MPD 0.15 */
@@ -292,8 +284,7 @@ handle_update(Client &client, unsigned argc, char *argv[], bool discard)
 	if (db != nullptr)
 		return handle_update(client, *db, path, discard);
 #else
-	(void)argc;
-	(void)argv;
+	(void)args;
 	(void)discard;
 #endif
 
@@ -302,24 +293,24 @@ handle_update(Client &client, unsigned argc, char *argv[], bool discard)
 }
 
 CommandResult
-handle_update(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_update(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
-	return handle_update(client, argc, argv, false);
+	return handle_update(client, args, false);
 }
 
 CommandResult
-handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rescan(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
-	return handle_update(client, argc, argv, true);
+	return handle_update(client, args, true);
 }
 
 CommandResult
-handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_setvol(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned level;
 	bool success;
 
-	if (!check_unsigned(client, &level, argv[1]))
+	if (!check_unsigned(client, &level, args.front()))
 		return CommandResult::ERROR;
 
 	if (level > 100) {
@@ -338,10 +329,10 @@ handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_volume(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_volume(Client &client, ConstBuffer<const char *> args)
 {
 	int relative;
-	if (!check_int(client, &relative, argv[1]))
+	if (!check_int(client, &relative, args.front()))
 		return CommandResult::ERROR;
 
 	if (relative < -100 || relative > 100) {
@@ -372,26 +363,24 @@ handle_volume(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_stats(Client &client,
-	     gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_stats(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	stats_print(client);
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_ping(gcc_unused Client &client,
-	    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_ping(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_password(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_password(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned permission = 0;
 
-	if (getPermissionFromPassword(argv[1], &permission) < 0) {
+	if (getPermissionFromPassword(args.front(), &permission) < 0) {
 		command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
 		return CommandResult::ERROR;
 	}
@@ -402,8 +391,7 @@ handle_password(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_config(Client &client,
-	      gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_config(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	if (!client.IsLocal()) {
 		command_error(client, ACK_ERROR_PERMISSION,
@@ -423,17 +411,16 @@ handle_config(Client &client,
 }
 
 CommandResult
-handle_idle(Client &client,
-	    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_idle(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned flags = 0;
 
-	for (unsigned i = 1; i < argc; ++i) {
-		unsigned event = idle_parse_name(argv[i]);
+	for (const char *i : args) {
+		unsigned event = idle_parse_name(i);
 		if (event == 0) {
 			command_error(client, ACK_ERROR_ARG,
 				      "Unrecognized idle event: %s",
-				      argv[i]);
+				      i);
 			return CommandResult::ERROR;
 		}
 
diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx
index 7cfa35dfb..a0076954e 100644
--- a/src/command/OtherCommands.hxx
+++ b/src/command/OtherCommands.hxx
@@ -23,53 +23,54 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_urlhandlers(Client &client, unsigned argc, char *argv[]);
+handle_urlhandlers(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_decoders(Client &client, unsigned argc, char *argv[]);
+handle_decoders(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_tagtypes(Client &client, unsigned argc, char *argv[]);
+handle_tagtypes(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_kill(Client &client, unsigned argc, char *argv[]);
+handle_kill(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_close(Client &client, unsigned argc, char *argv[]);
+handle_close(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_listfiles(Client &client, unsigned argc, char *argv[]);
+handle_listfiles(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_lsinfo(Client &client, unsigned argc, char *argv[]);
+handle_lsinfo(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_update(Client &client, unsigned argc, char *argv[]);
+handle_update(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_rescan(Client &client, unsigned argc, char *argv[]);
+handle_rescan(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_setvol(Client &client, unsigned argc, char *argv[]);
+handle_setvol(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_volume(Client &client, unsigned argc, char *argv[]);
+handle_volume(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_stats(Client &client, unsigned argc, char *argv[]);
+handle_stats(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_ping(Client &client, unsigned argc, char *argv[]);
+handle_ping(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_password(Client &client, unsigned argc, char *argv[]);
+handle_password(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_config(Client &client, unsigned argc, char *argv[]);
+handle_config(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_idle(Client &client, unsigned argc, char *argv[]);
+handle_idle(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index c69a0dd65..5b0894310 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -25,12 +25,15 @@
 #include "protocol/ArgParser.hxx"
 #include "client/Client.hxx"
 #include "Partition.hxx"
+#include "util/ConstBuffer.hxx"
 
 CommandResult
-handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_enableoutput(Client &client, ConstBuffer<const char *> args)
 {
+	assert(args.size == 1);
+
 	unsigned device;
-	if (!check_unsigned(client, &device, argv[1]))
+	if (!check_unsigned(client, &device, args.front()))
 		return CommandResult::ERROR;
 
 	if (!audio_output_enable_index(client.partition.outputs, device)) {
@@ -43,10 +46,12 @@ handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_disableoutput(Client &client, ConstBuffer<const char *> args)
 {
+	assert(args.size == 1);
+
 	unsigned device;
-	if (!check_unsigned(client, &device, argv[1]))
+	if (!check_unsigned(client, &device, args.front()))
 		return CommandResult::ERROR;
 
 	if (!audio_output_disable_index(client.partition.outputs, device)) {
@@ -59,10 +64,12 @@ handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_toggleoutput(Client &client, ConstBuffer<const char *> args)
 {
+	assert(args.size == 1);
+
 	unsigned device;
-	if (!check_unsigned(client, &device, argv[1]))
+	if (!check_unsigned(client, &device, args.front()))
 		return CommandResult::ERROR;
 
 	if (!audio_output_toggle_index(client.partition.outputs, device)) {
@@ -75,9 +82,10 @@ handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_devices(Client &client,
-	       gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_devices(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
+	assert(args.IsEmpty());
+
 	printAudioDevices(client, client.partition.outputs);
 
 	return CommandResult::OK;
diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx
index 8d6be0511..d550791ac 100644
--- a/src/command/OutputCommands.hxx
+++ b/src/command/OutputCommands.hxx
@@ -23,17 +23,18 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_enableoutput(Client &client, unsigned argc, char *argv[]);
+handle_enableoutput(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_disableoutput(Client &client, unsigned argc, char *argv[]);
+handle_disableoutput(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_toggleoutput(Client &client, unsigned argc, char *argv[]);
+handle_toggleoutput(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_devices(Client &client, unsigned argc, char *argv[]);
+handle_devices(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx
index a320fb6ba..c24088f9a 100644
--- a/src/command/PlayerCommands.cxx
+++ b/src/command/PlayerCommands.cxx
@@ -30,6 +30,7 @@
 #include "protocol/ArgParser.hxx"
 #include "AudioFormat.hxx"
 #include "ReplayGainConfig.hxx"
+#include "util/ConstBuffer.hxx"
 
 #ifdef ENABLE_DATABASE
 #include "db/update/Service.hxx"
@@ -56,22 +57,22 @@
 #define COMMAND_STATUS_UPDATING_DB	"updating_db"
 
 CommandResult
-handle_play(Client &client, unsigned argc, char *argv[])
+handle_play(Client &client, ConstBuffer<const char *> args)
 {
 	int song = -1;
 
-	if (argc == 2 && !check_int(client, &song, argv[1]))
+	if (!args.IsEmpty() && !check_int(client, &song, args.front()))
 		return CommandResult::ERROR;
 	PlaylistResult result = client.partition.PlayPosition(song);
 	return print_playlist_result(client, result);
 }
 
 CommandResult
-handle_playid(Client &client, unsigned argc, char *argv[])
+handle_playid(Client &client, ConstBuffer<const char *> args)
 {
 	int id = -1;
 
-	if (argc == 2 && !check_int(client, &id, argv[1]))
+	if (!args.IsEmpty() && !check_int(client, &id, args.front()))
 		return CommandResult::ERROR;
 
 	PlaylistResult result = client.partition.PlayId(id);
@@ -79,28 +80,25 @@ handle_playid(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_stop(Client &client,
-	    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_stop(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	client.partition.Stop();
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_currentsong(Client &client,
-		   gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_currentsong(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	playlist_print_current(client, client.playlist);
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_pause(Client &client,
-	     unsigned argc, char *argv[])
+handle_pause(Client &client, ConstBuffer<const char *> args)
 {
-	if (argc == 2) {
+	if (!args.IsEmpty()) {
 		bool pause_flag;
-		if (!check_bool(client, &pause_flag, argv[1]))
+		if (!check_bool(client, &pause_flag, args.front()))
 			return CommandResult::ERROR;
 
 		client.player_control.SetPause(pause_flag);
@@ -111,8 +109,7 @@ handle_pause(Client &client,
 }
 
 CommandResult
-handle_status(Client &client,
-	      gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_status(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	const char *state = nullptr;
 	int song;
@@ -226,8 +223,7 @@ handle_status(Client &client,
 }
 
 CommandResult
-handle_next(Client &client,
-	    gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_next(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	playlist &playlist = client.playlist;
 
@@ -243,18 +239,17 @@ handle_next(Client &client,
 }
 
 CommandResult
-handle_previous(Client &client,
-		gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_previous(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	client.partition.PlayPrevious();
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_repeat(Client &client, ConstBuffer<const char *> args)
 {
 	bool status;
-	if (!check_bool(client, &status, argv[1]))
+	if (!check_bool(client, &status, args.front()))
 		return CommandResult::ERROR;
 
 	client.partition.SetRepeat(status);
@@ -262,10 +257,10 @@ handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_single(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_single(Client &client, ConstBuffer<const char *> args)
 {
 	bool status;
-	if (!check_bool(client, &status, argv[1]))
+	if (!check_bool(client, &status, args.front()))
 		return CommandResult::ERROR;
 
 	client.partition.SetSingle(status);
@@ -273,10 +268,10 @@ handle_single(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_consume(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_consume(Client &client, ConstBuffer<const char *> args)
 {
 	bool status;
-	if (!check_bool(client, &status, argv[1]))
+	if (!check_bool(client, &status, args.front()))
 		return CommandResult::ERROR;
 
 	client.partition.SetConsume(status);
@@ -284,10 +279,10 @@ handle_consume(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_random(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_random(Client &client, ConstBuffer<const char *> args)
 {
 	bool status;
-	if (!check_bool(client, &status, argv[1]))
+	if (!check_bool(client, &status, args.front()))
 		return CommandResult::ERROR;
 
 	client.partition.SetRandom(status);
@@ -296,22 +291,21 @@ handle_random(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_clearerror(gcc_unused Client &client,
-		  gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_clearerror(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	client.player_control.ClearError();
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_seek(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_seek(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned song;
 	SongTime seek_time;
 
-	if (!check_unsigned(client, &song, argv[1]))
+	if (!check_unsigned(client, &song, args[0]))
 		return CommandResult::ERROR;
-	if (!ParseCommandArg(client, seek_time, argv[2]))
+	if (!ParseCommandArg(client, seek_time, args[1]))
 		return CommandResult::ERROR;
 
 	PlaylistResult result =
@@ -320,14 +314,14 @@ handle_seek(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_seekid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned id;
 	SongTime seek_time;
 
-	if (!check_unsigned(client, &id, argv[1]))
+	if (!check_unsigned(client, &id, args[0]))
 		return CommandResult::ERROR;
-	if (!ParseCommandArg(client, seek_time, argv[2]))
+	if (!ParseCommandArg(client, seek_time, args[1]))
 		return CommandResult::ERROR;
 
 	PlaylistResult result =
@@ -336,9 +330,9 @@ handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_seekcur(Client &client, ConstBuffer<const char *> args)
 {
-	const char *p = argv[1];
+	const char *p = args.front();
 	bool relative = *p == '+' || *p == '-';
 	SignedSongTime seek_time;
 	if (!ParseCommandArg(client, seek_time, p))
@@ -350,11 +344,11 @@ handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_crossfade(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned xfade_time;
 
-	if (!check_unsigned(client, &xfade_time, argv[1]))
+	if (!check_unsigned(client, &xfade_time, args.front()))
 		return CommandResult::ERROR;
 	client.player_control.SetCrossFade(xfade_time);
 
@@ -362,11 +356,11 @@ handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_mixrampdb(Client &client, ConstBuffer<const char *> args)
 {
 	float db;
 
-	if (!check_float(client, &db, argv[1]))
+	if (!check_float(client, &db, args.front()))
 		return CommandResult::ERROR;
 	client.player_control.SetMixRampDb(db);
 
@@ -374,11 +368,11 @@ handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_mixrampdelay(Client &client, ConstBuffer<const char *> args)
 {
 	float delay_secs;
 
-	if (!check_float(client, &delay_secs, argv[1]))
+	if (!check_float(client, &delay_secs, args.front()))
 		return CommandResult::ERROR;
 	client.player_control.SetMixRampDelay(delay_secs);
 
@@ -386,10 +380,9 @@ handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_replay_gain_mode(Client &client,
-			gcc_unused unsigned argc, char *argv[])
+handle_replay_gain_mode(Client &client, ConstBuffer<const char *> args)
 {
-	if (!replay_gain_set_mode_string(argv[1])) {
+	if (!replay_gain_set_mode_string(args.front())) {
 		command_error(client, ACK_ERROR_ARG,
 			      "Unrecognized replay gain mode");
 		return CommandResult::ERROR;
@@ -400,8 +393,7 @@ handle_replay_gain_mode(Client &client,
 }
 
 CommandResult
-handle_replay_gain_status(Client &client,
-			  gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_replay_gain_status(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	client_printf(client, "replay_gain_mode: %s\n",
 		      replay_gain_get_mode_string());
diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx
index da7083f1e..492a4aced 100644
--- a/src/command/PlayerCommands.hxx
+++ b/src/command/PlayerCommands.hxx
@@ -23,68 +23,69 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_play(Client &client, unsigned argc, char *argv[]);
+handle_play(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playid(Client &client, unsigned argc, char *argv[]);
+handle_playid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_stop(Client &client, unsigned argc, char *argv[]);
+handle_stop(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_currentsong(Client &client, unsigned argc, char *argv[]);
+handle_currentsong(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_pause(Client &client, unsigned argc, char *argv[]);
+handle_pause(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_status(Client &client, unsigned argc, char *argv[]);
+handle_status(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_next(Client &client, unsigned argc, char *argv[]);
+handle_next(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_previous(Client &client, unsigned argc, char *avg[]);
+handle_previous(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_repeat(Client &client, unsigned argc, char *argv[]);
+handle_repeat(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_single(Client &client, unsigned argc, char *argv[]);
+handle_single(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_consume(Client &client, unsigned argc, char *argv[]);
+handle_consume(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_random(Client &client, unsigned argc, char *argv[]);
+handle_random(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_clearerror(Client &client, unsigned argc, char *argv[]);
+handle_clearerror(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_seek(Client &client, unsigned argc, char *argv[]);
+handle_seek(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_seekid(Client &client, unsigned argc, char *argv[]);
+handle_seekid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_seekcur(Client &client, unsigned argc, char *argv[]);
+handle_seekcur(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_crossfade(Client &client, unsigned argc, char *argv[]);
+handle_crossfade(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_mixrampdb(Client &client, unsigned argc, char *argv[]);
+handle_mixrampdb(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_mixrampdelay(Client &client, unsigned argc, char *argv[]);
+handle_mixrampdelay(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]);
+handle_replay_gain_mode(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_replay_gain_status(Client &client, unsigned argc, char *argv[]);
+handle_replay_gain_status(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx
index 593eab865..4abc88031 100644
--- a/src/command/PlaylistCommands.cxx
+++ b/src/command/PlaylistCommands.cxx
@@ -39,6 +39,7 @@
 #include "fs/AllocatedPath.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
 
 bool
 playlist_commands_available()
@@ -58,28 +59,28 @@ print_spl_list(Client &client, const PlaylistVector &list)
 }
 
 CommandResult
-handle_save(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_save(Client &client, ConstBuffer<const char *> args)
 {
-	PlaylistResult result = spl_save_playlist(argv[1], client.playlist);
+	PlaylistResult result = spl_save_playlist(args.front(), client.playlist);
 	return print_playlist_result(client, result);
 }
 
 CommandResult
-handle_load(Client &client, unsigned argc, char *argv[])
+handle_load(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned start_index, end_index;
 
-	if (argc < 3) {
+	if (args.size < 2) {
 		start_index = 0;
 		end_index = unsigned(-1);
-	} else if (!check_range(client, &start_index, &end_index, argv[2]))
+	} else if (!check_range(client, &start_index, &end_index, args[1]))
 		return CommandResult::ERROR;
 
 	const ScopeBulkEdit bulk_edit(client.partition);
 
 	Error error;
 	const SongLoader loader(client);
-	if (!playlist_open_into_queue(argv[1],
+	if (!playlist_open_into_queue(args.front(),
 				      start_index, end_index,
 				      client.playlist,
 				      client.player_control, loader, error))
@@ -89,94 +90,104 @@ handle_load(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_listplaylist(Client &client, ConstBuffer<const char *> args)
 {
-	if (playlist_file_print(client, argv[1], false))
+	const char *const name = args.front();
+
+	if (playlist_file_print(client, name, false))
 		return CommandResult::OK;
 
 	Error error;
-	return spl_print(client, argv[1], false, error)
+	return spl_print(client, name, false, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_listplaylistinfo(Client &client,
-			gcc_unused unsigned argc, char *argv[])
+handle_listplaylistinfo(Client &client, ConstBuffer<const char *> args)
 {
-	if (playlist_file_print(client, argv[1], true))
+	const char *const name = args.front();
+
+	if (playlist_file_print(client, name, true))
 		return CommandResult::OK;
 
 	Error error;
-	return spl_print(client, argv[1], true, error)
+	return spl_print(client, name, true, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_rm(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rm(Client &client, ConstBuffer<const char *> args)
 {
+	const char *const name = args.front();
+
 	Error error;
-	return spl_delete(argv[1], error)
+	return spl_delete(name, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_rename(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rename(Client &client, ConstBuffer<const char *> args)
 {
+	const char *const old_name = args[0];
+	const char *const new_name = args[1];
+
 	Error error;
-	return spl_rename(argv[1], argv[2], error)
+	return spl_rename(old_name, new_name, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_playlistdelete(Client &client,
-		      gcc_unused unsigned argc, char *argv[]) {
-	char *playlist = argv[1];
+handle_playlistdelete(Client &client, ConstBuffer<const char *> args)
+{
+	const char *const name = args[0];
 	unsigned from;
 
-	if (!check_unsigned(client, &from, argv[2]))
+	if (!check_unsigned(client, &from, args[1]))
 		return CommandResult::ERROR;
 
 	Error error;
-	return spl_remove_index(playlist, from, error)
+	return spl_remove_index(name, from, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_playlistmove(Client &client, ConstBuffer<const char *> args)
 {
-	char *playlist = argv[1];
+	const char *const name = args.front();
 	unsigned from, to;
 
-	if (!check_unsigned(client, &from, argv[2]))
+	if (!check_unsigned(client, &from, args[1]))
 		return CommandResult::ERROR;
-	if (!check_unsigned(client, &to, argv[3]))
+	if (!check_unsigned(client, &to, args[2]))
 		return CommandResult::ERROR;
 
 	Error error;
-	return spl_move_index(playlist, from, to, error)
+	return spl_move_index(name, from, to, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_playlistclear(Client &client, ConstBuffer<const char *> args)
 {
+	const char *const name = args.front();
+
 	Error error;
-	return spl_clear(argv[1], error)
+	return spl_clear(name, error)
 		? CommandResult::OK
 		: print_error(client, error);
 }
 
 CommandResult
-handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_playlistadd(Client &client, ConstBuffer<const char *> args)
 {
-	char *playlist = argv[1];
-	char *uri = argv[2];
+	const char *const playlist = args[0];
+	const char *const uri = args[1];
 
 	bool success;
 	Error error;
@@ -207,8 +218,7 @@ handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_listplaylists(Client &client,
-		     gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_listplaylists(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	Error error;
 	const auto list = ListPlaylistFiles(error);
diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx
index 6dc589c8b..91721899c 100644
--- a/src/command/PlaylistCommands.hxx
+++ b/src/command/PlaylistCommands.hxx
@@ -24,42 +24,43 @@
 #include "Compiler.h"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 gcc_const
 bool
 playlist_commands_available();
 
 CommandResult
-handle_save(Client &client, unsigned argc, char *argv[]);
+handle_save(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_load(Client &client, unsigned argc, char *argv[]);
+handle_load(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_listplaylist(Client &client, unsigned argc, char *argv[]);
+handle_listplaylist(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]);
+handle_listplaylistinfo(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_rm(Client &client, unsigned argc, char *argv[]);
+handle_rm(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_rename(Client &client, unsigned argc, char *argv[]);
+handle_rename(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistdelete(Client &client, unsigned argc, char *argv[]);
+handle_playlistdelete(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistmove(Client &client, unsigned argc, char *argv[]);
+handle_playlistmove(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistclear(Client &client, unsigned argc, char *argv[]);
+handle_playlistclear(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistadd(Client &client, unsigned argc, char *argv[]);
+handle_playlistadd(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_listplaylists(Client &client, unsigned argc, char *argv[]);
+handle_listplaylists(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx
index d0b789eb1..e94c7bc8b 100644
--- a/src/command/QueueCommands.cxx
+++ b/src/command/QueueCommands.cxx
@@ -59,9 +59,9 @@ translate_uri(Client &client, const char *uri)
 }
 
 CommandResult
-handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_add(Client &client, ConstBuffer<const char *> args)
 {
-	const char *uri = argv[1];
+	const char *uri = args.front();
 	if (memcmp(uri, "/", 2) == 0)
 		/* this URI is malformed, but some clients are buggy
 		   and use "add /" to add the whole database, which
@@ -99,9 +99,9 @@ handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_addid(Client &client, unsigned argc, char *argv[])
+handle_addid(Client &client, ConstBuffer<const char *> args)
 {
-	const char *const uri = translate_uri(client, argv[1]);
+	const char *const uri = translate_uri(client, args.front());
 	if (uri == nullptr)
 		return CommandResult::ERROR;
 
@@ -111,9 +111,9 @@ handle_addid(Client &client, unsigned argc, char *argv[])
 	if (added_id == 0)
 		return print_error(client, error);
 
-	if (argc == 3) {
+	if (args.size == 2) {
 		unsigned to;
-		if (!check_unsigned(client, &to, argv[2]))
+		if (!check_unsigned(client, &to, args[1]))
 			return CommandResult::ERROR;
 		PlaylistResult result = client.partition.MoveId(added_id, to);
 		if (result != PlaylistResult::SUCCESS) {
@@ -160,14 +160,14 @@ parse_time_range(const char *p, SongTime &start_r, SongTime &end_r)
 }
 
 CommandResult
-handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_rangeid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned id;
-	if (!check_unsigned(client, &id, argv[1]))
+	if (!check_unsigned(client, &id, args.front()))
 		return CommandResult::ERROR;
 
 	SongTime start, end;
-	if (!parse_time_range(argv[2], start, end)) {
+	if (!parse_time_range(args[1], start, end)) {
 		command_error(client, ACK_ERROR_ARG, "Bad range");
 		return CommandResult::ERROR;
 	}
@@ -182,11 +182,11 @@ handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_delete(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned start, end;
 
-	if (!check_range(client, &start, &end, argv[1]))
+	if (!check_range(client, &start, &end, args.front()))
 		return CommandResult::ERROR;
 
 	PlaylistResult result = client.partition.DeleteRange(start, end);
@@ -194,11 +194,11 @@ handle_delete(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_deleteid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned id;
 
-	if (!check_unsigned(client, &id, argv[1]))
+	if (!check_unsigned(client, &id, args.front()))
 		return CommandResult::ERROR;
 
 	PlaylistResult result = client.partition.DeleteId(id);
@@ -206,19 +206,17 @@ handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_playlist(Client &client,
-		gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_playlist(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	playlist_print_uris(client, client.playlist);
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_shuffle(gcc_unused Client &client,
-	       gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_shuffle(gcc_unused Client &client, ConstBuffer<const char *> args)
 {
 	unsigned start = 0, end = client.playlist.queue.GetLength();
-	if (argc == 2 && !check_range(client, &start, &end, argv[1]))
+	if (args.size == 1 && !check_range(client, &start, &end, args.front()))
 		return CommandResult::ERROR;
 
 	client.partition.Shuffle(start, end);
@@ -226,19 +224,18 @@ handle_shuffle(gcc_unused Client &client,
 }
 
 CommandResult
-handle_clear(gcc_unused Client &client,
-	     gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_clear(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	client.partition.ClearQueue();
 	return CommandResult::OK;
 }
 
 CommandResult
-handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_plchanges(Client &client, ConstBuffer<const char *> args)
 {
 	uint32_t version;
 
-	if (!check_uint32(client, &version, argv[1]))
+	if (!check_uint32(client, &version, args.front()))
 		return CommandResult::ERROR;
 
 	playlist_print_changes_info(client, client.playlist, version);
@@ -246,11 +243,11 @@ handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_plchangesposid(Client &client, ConstBuffer<const char *> args)
 {
 	uint32_t version;
 
-	if (!check_uint32(client, &version, argv[1]))
+	if (!check_uint32(client, &version, args.front()))
 		return CommandResult::ERROR;
 
 	playlist_print_changes_position(client, client.playlist, version);
@@ -258,12 +255,12 @@ handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_playlistinfo(Client &client, unsigned argc, char *argv[])
+handle_playlistinfo(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned start = 0, end = std::numeric_limits<unsigned>::max();
 	bool ret;
 
-	if (argc == 2 && !check_range(client, &start, &end, argv[1]))
+	if (args.size == 1 && !check_range(client, &start, &end, args.front()))
 		return CommandResult::ERROR;
 
 	ret = playlist_print_info(client, client.playlist, start, end);
@@ -275,11 +272,11 @@ handle_playlistinfo(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_playlistid(Client &client, unsigned argc, char *argv[])
+handle_playlistid(Client &client, ConstBuffer<const char *> args)
 {
-	if (argc >= 2) {
+	if (!args.IsEmpty()) {
 		unsigned id;
-		if (!check_unsigned(client, &id, argv[1]))
+		if (!check_unsigned(client, &id, args.front()))
 			return CommandResult::ERROR;
 
 		bool ret = playlist_print_id(client, client.playlist, id);
@@ -295,11 +292,9 @@ handle_playlistid(Client &client, unsigned argc, char *argv[])
 }
 
 static CommandResult
-handle_playlist_match(Client &client, unsigned argc, char *argv[],
+handle_playlist_match(Client &client, ConstBuffer<const char *> args,
 		      bool fold_case)
 {
-	ConstBuffer<const char *> args(argv + 1, argc - 1);
-
 	SongFilter filter;
 	if (!filter.Parse(args, fold_case)) {
 		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
@@ -311,35 +306,35 @@ handle_playlist_match(Client &client, unsigned argc, char *argv[],
 }
 
 CommandResult
-handle_playlistfind(Client &client, unsigned argc, char *argv[])
+handle_playlistfind(Client &client, ConstBuffer<const char *> args)
 {
-	return handle_playlist_match(client, argc, argv, false);
+	return handle_playlist_match(client, args, false);
 }
 
 CommandResult
-handle_playlistsearch(Client &client, unsigned argc, char *argv[])
+handle_playlistsearch(Client &client, ConstBuffer<const char *> args)
 {
-	return handle_playlist_match(client, argc, argv, true);
+	return handle_playlist_match(client, args, true);
 }
 
 CommandResult
-handle_prio(Client &client, unsigned argc, char *argv[])
+handle_prio(Client &client, ConstBuffer<const char *> args)
 {
+	const char *const priority_string = args.shift();
 	unsigned priority;
 
-	if (!check_unsigned(client, &priority, argv[1]))
+	if (!check_unsigned(client, &priority, priority_string))
 		return CommandResult::ERROR;
 
 	if (priority > 0xff) {
 		command_error(client, ACK_ERROR_ARG,
-			      "Priority out of range: %s", argv[1]);
+			      "Priority out of range: %s", priority_string);
 		return CommandResult::ERROR;
 	}
 
-	for (unsigned i = 2; i < argc; ++i) {
+	for (const char *i : args) {
 		unsigned start_position, end_position;
-		if (!check_range(client, &start_position, &end_position,
-				 argv[i]))
+		if (!check_range(client, &start_position, &end_position, i))
 			return CommandResult::ERROR;
 
 		PlaylistResult result =
@@ -354,22 +349,23 @@ handle_prio(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_prioid(Client &client, unsigned argc, char *argv[])
+handle_prioid(Client &client, ConstBuffer<const char *> args)
 {
+	const char *const priority_string = args.shift();
 	unsigned priority;
 
-	if (!check_unsigned(client, &priority, argv[1]))
+	if (!check_unsigned(client, &priority, priority_string))
 		return CommandResult::ERROR;
 
 	if (priority > 0xff) {
 		command_error(client, ACK_ERROR_ARG,
-			      "Priority out of range: %s", argv[1]);
+			      "Priority out of range: %s", priority_string);
 		return CommandResult::ERROR;
 	}
 
-	for (unsigned i = 2; i < argc; ++i) {
+	for (const char *i : args) {
 		unsigned song_id;
-		if (!check_unsigned(client, &song_id, argv[i]))
+		if (!check_unsigned(client, &song_id, i))
 			return CommandResult::ERROR;
 
 		PlaylistResult result =
@@ -382,14 +378,14 @@ handle_prioid(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_move(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_move(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned start, end;
 	int to;
 
-	if (!check_range(client, &start, &end, argv[1]))
+	if (!check_range(client, &start, &end, args[0]))
 		return CommandResult::ERROR;
-	if (!check_int(client, &to, argv[2]))
+	if (!check_int(client, &to, args[1]))
 		return CommandResult::ERROR;
 
 	PlaylistResult result =
@@ -398,27 +394,27 @@ handle_move(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_moveid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned id;
 	int to;
 
-	if (!check_unsigned(client, &id, argv[1]))
+	if (!check_unsigned(client, &id, args[0]))
 		return CommandResult::ERROR;
-	if (!check_int(client, &to, argv[2]))
+	if (!check_int(client, &to, args[1]))
 		return CommandResult::ERROR;
 	PlaylistResult result = client.partition.MoveId(id, to);
 	return print_playlist_result(client, result);
 }
 
 CommandResult
-handle_swap(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_swap(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned song1, song2;
 
-	if (!check_unsigned(client, &song1, argv[1]))
+	if (!check_unsigned(client, &song1, args[0]))
 		return CommandResult::ERROR;
-	if (!check_unsigned(client, &song2, argv[2]))
+	if (!check_unsigned(client, &song2, args[1]))
 		return CommandResult::ERROR;
 
 	PlaylistResult result =
@@ -427,13 +423,13 @@ handle_swap(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_swapid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned id1, id2;
 
-	if (!check_unsigned(client, &id1, argv[1]))
+	if (!check_unsigned(client, &id1, args[0]))
 		return CommandResult::ERROR;
-	if (!check_unsigned(client, &id2, argv[2]))
+	if (!check_unsigned(client, &id2, args[1]))
 		return CommandResult::ERROR;
 
 	PlaylistResult result = client.partition.SwapIds(id1, id2);
diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx
index f98f7bad2..5193e8b65 100644
--- a/src/command/QueueCommands.hxx
+++ b/src/command/QueueCommands.hxx
@@ -23,65 +23,66 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_add(Client &client, unsigned argc, char *argv[]);
+handle_add(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_addid(Client &client, unsigned argc, char *argv[]);
+handle_addid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_rangeid(Client &client, unsigned argc, char *argv[]);
+handle_rangeid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_delete(Client &client, unsigned argc, char *argv[]);
+handle_delete(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_deleteid(Client &client, unsigned argc, char *argv[]);
+handle_deleteid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlist(Client &client, unsigned argc, char *argv[]);
+handle_playlist(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_shuffle(Client &client, unsigned argc, char *argv[]);
+handle_shuffle(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_clear(Client &client, unsigned argc, char *argv[]);
+handle_clear(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_plchanges(Client &client, unsigned argc, char *argv[]);
+handle_plchanges(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_plchangesposid(Client &client, unsigned argc, char *argv[]);
+handle_plchangesposid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistinfo(Client &client, unsigned argc, char *argv[]);
+handle_playlistinfo(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistid(Client &client, unsigned argc, char *argv[]);
+handle_playlistid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistfind(Client &client, unsigned argc, char *argv[]);
+handle_playlistfind(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_playlistsearch(Client &client, unsigned argc, char *argv[]);
+handle_playlistsearch(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_prio(Client &client, unsigned argc, char *argv[]);
+handle_prio(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_prioid(Client &client, unsigned argc, char *argv[]);
+handle_prioid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_move(Client &client, unsigned argc, char *argv[]);
+handle_move(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_moveid(Client &client, unsigned argc, char *argv[]);
+handle_moveid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_swap(Client &client, unsigned argc, char *argv[]);
+handle_swap(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_swapid(Client &client, unsigned argc, char *argv[]);
+handle_swapid(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx
index 37506d51b..088a07a79 100644
--- a/src/command/StickerCommands.cxx
+++ b/src/command/StickerCommands.cxx
@@ -31,6 +31,7 @@
 #include "Partition.hxx"
 #include "Instance.hxx"
 #include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
 
 #include <string.h>
 
@@ -51,20 +52,22 @@ sticker_song_find_print_cb(const LightSong &song, const char *value,
 }
 
 static CommandResult
-handle_sticker_song(Client &client, unsigned argc, char *argv[])
+handle_sticker_song(Client &client, ConstBuffer<const char *> args)
 {
 	Error error;
 	const Database *db = client.GetDatabase(error);
 	if (db == nullptr)
 		return print_error(client, error);
 
+	const char *const cmd = args.front();
+
 	/* get song song_id key */
-	if (argc == 5 && strcmp(argv[1], "get") == 0) {
-		const LightSong *song = db->GetSong(argv[3], error);
+	if (args.size == 4 && strcmp(cmd, "get") == 0) {
+		const LightSong *song = db->GetSong(args[2], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
-		const auto value = sticker_song_get_value(*song, argv[4]);
+		const auto value = sticker_song_get_value(*song, args[3]);
 		db->ReturnSong(song);
 		if (value.empty()) {
 			command_error(client, ACK_ERROR_NO_EXIST,
@@ -72,12 +75,12 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
 			return CommandResult::ERROR;
 		}
 
-		sticker_print_value(client, argv[4], value.c_str());
+		sticker_print_value(client, args[3], value.c_str());
 
 		return CommandResult::OK;
 	/* list song song_id */
-	} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
-		const LightSong *song = db->GetSong(argv[3], error);
+	} else if (args.size == 3 && strcmp(cmd, "list") == 0) {
+		const LightSong *song = db->GetSong(args[2], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
@@ -90,12 +93,12 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
 
 		return CommandResult::OK;
 	/* set song song_id id key */
-	} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
-		const LightSong *song = db->GetSong(argv[3], error);
+	} else if (args.size == 5 && strcmp(cmd, "set") == 0) {
+		const LightSong *song = db->GetSong(args[2], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
-		bool ret = sticker_song_set_value(*song, argv[4], argv[5]);
+		bool ret = sticker_song_set_value(*song, args[3], args[4]);
 		db->ReturnSong(song);
 		if (!ret) {
 			command_error(client, ACK_ERROR_SYSTEM,
@@ -105,15 +108,15 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
 
 		return CommandResult::OK;
 	/* delete song song_id [key] */
-	} else if ((argc == 4 || argc == 5) &&
-		   strcmp(argv[1], "delete") == 0) {
-		const LightSong *song = db->GetSong(argv[3], error);
+	} else if ((args.size == 3 || args.size == 4) &&
+		   strcmp(cmd, "delete") == 0) {
+		const LightSong *song = db->GetSong(args[2], error);
 		if (song == nullptr)
 			return print_error(client, error);
 
-		bool ret = argc == 4
+		bool ret = args.size == 3
 			? sticker_song_delete(*song)
-			: sticker_song_delete_value(*song, argv[4]);
+			: sticker_song_delete_value(*song, args[3]);
 		db->ReturnSong(song);
 		if (!ret) {
 			command_error(client, ACK_ERROR_SYSTEM,
@@ -123,15 +126,15 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
 
 		return CommandResult::OK;
 	/* find song dir key */
-	} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
+	} else if (args.size == 4 && strcmp(cmd, "find") == 0) {
 		/* "sticker find song a/directory name" */
 
-		const char *const base_uri = argv[3];
+		const char *const base_uri = args[2];
 
 		bool success;
 		struct sticker_song_find_data data = {
 			client,
-			argv[4],
+			args[3],
 		};
 
 		success = sticker_song_find(*db, base_uri, data.name,
@@ -150,9 +153,9 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_sticker(Client &client, unsigned argc, char *argv[])
+handle_sticker(Client &client, ConstBuffer<const char *> args)
 {
-	assert(argc >= 4);
+	assert(args.size >= 3);
 
 	if (!sticker_enabled()) {
 		command_error(client, ACK_ERROR_UNKNOWN,
@@ -160,8 +163,8 @@ handle_sticker(Client &client, unsigned argc, char *argv[])
 		return CommandResult::ERROR;
 	}
 
-	if (strcmp(argv[2], "song") == 0)
-		return handle_sticker_song(client, argc, argv);
+	if (strcmp(args[1], "song") == 0)
+		return handle_sticker_song(client, args);
 	else {
 		command_error(client, ACK_ERROR_ARG,
 			      "unknown sticker domain");
diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx
index cf46cd034..0e8d765fa 100644
--- a/src/command/StickerCommands.hxx
+++ b/src/command/StickerCommands.hxx
@@ -23,8 +23,9 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_sticker(Client &client, unsigned argc, char *argv[]);
+handle_sticker(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx
index ee51c573e..510e9f1b1 100644
--- a/src/command/StorageCommands.cxx
+++ b/src/command/StorageCommands.cxx
@@ -25,6 +25,7 @@
 #include "protocol/Result.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
 #include "fs/Traits.hxx"
 #include "client/Client.hxx"
 #include "Partition.hxx"
@@ -167,7 +168,7 @@ print_storage_uri(Client &client, const Storage &storage)
 }
 
 CommandResult
-handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[])
+handle_listmounts(Client &client, gcc_unused ConstBuffer<const char *> args)
 {
 	Storage *_composite = client.partition.instance.storage;
 	if (_composite == nullptr) {
@@ -189,7 +190,7 @@ handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *arg
 }
 
 CommandResult
-handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_mount(Client &client, ConstBuffer<const char *> args)
 {
 	Storage *_composite = client.partition.instance.storage;
 	if (_composite == nullptr) {
@@ -199,8 +200,8 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
 
 	CompositeStorage &composite = *(CompositeStorage *)_composite;
 
-	const char *const local_uri = argv[1];
-	const char *const remote_uri = argv[2];
+	const char *const local_uri = args[0];
+	const char *const remote_uri = args[1];
 
 	if (*local_uri == 0) {
 		command_error(client, ACK_ERROR_ARG, "Bad mount point");
@@ -252,7 +253,7 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_unmount(Client &client, ConstBuffer<const char *> args)
 {
 	Storage *_composite = client.partition.instance.storage;
 	if (_composite == nullptr) {
@@ -262,7 +263,7 @@ handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[])
 
 	CompositeStorage &composite = *(CompositeStorage *)_composite;
 
-	const char *const local_uri = argv[1];
+	const char *const local_uri = args.front();
 
 	if (*local_uri == 0) {
 		command_error(client, ACK_ERROR_ARG, "Bad mount point");
diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx
index a3636d54a..ce3622c4b 100644
--- a/src/command/StorageCommands.hxx
+++ b/src/command/StorageCommands.hxx
@@ -24,6 +24,7 @@
 
 class Client;
 class Storage;
+template<typename T> struct ConstBuffer;
 
 CommandResult
 handle_listfiles_storage(Client &client, Storage &storage, const char *uri);
@@ -32,12 +33,12 @@ CommandResult
 handle_listfiles_storage(Client &client, const char *uri);
 
 CommandResult
-handle_listmounts(Client &client, unsigned argc, char *argv[]);
+handle_listmounts(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_mount(Client &client, unsigned argc, char *argv[]);
+handle_mount(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_unmount(Client &client, unsigned argc, char *argv[]);
+handle_unmount(Client &client, ConstBuffer<const char *> args);
 
 #endif
diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx
index 2d537671c..6f821f451 100644
--- a/src/command/TagCommands.cxx
+++ b/src/command/TagCommands.cxx
@@ -25,15 +25,16 @@
 #include "protocol/Result.hxx"
 #include "tag/Tag.hxx"
 #include "Partition.hxx"
+#include "util/ConstBuffer.hxx"
 
 CommandResult
-handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
+handle_addtagid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned song_id;
-	if (!check_unsigned(client, &song_id, argv[1]))
+	if (!check_unsigned(client, &song_id, args.front()))
 		return CommandResult::ERROR;
 
-	const char *const tag_name = argv[2];
+	const char *const tag_name = args[1];
 	const TagType tag_type = tag_name_parse_i(tag_name);
 	if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
 		command_error(client, ACK_ERROR_ARG,
@@ -41,7 +42,7 @@ handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
 		return CommandResult::ERROR;
 	}
 
-	const char *const value = argv[3];
+	const char *const value = args[2];
 
 	Error error;
 	if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value,
@@ -52,15 +53,15 @@ handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[])
 }
 
 CommandResult
-handle_cleartagid(Client &client, unsigned argc, char *argv[])
+handle_cleartagid(Client &client, ConstBuffer<const char *> args)
 {
 	unsigned song_id;
-	if (!check_unsigned(client, &song_id, argv[1]))
+	if (!check_unsigned(client, &song_id, args.front()))
 		return CommandResult::ERROR;
 
 	TagType tag_type = TAG_NUM_OF_ITEM_TYPES;
-	if (argc >= 3) {
-		const char *const tag_name = argv[2];
+	if (args.size >= 2) {
+		const char *const tag_name = args[1];
 		tag_type = tag_name_parse_i(tag_name);
 		if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
 			command_error(client, ACK_ERROR_ARG,
diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx
index 748838e68..bc813b151 100644
--- a/src/command/TagCommands.hxx
+++ b/src/command/TagCommands.hxx
@@ -23,11 +23,12 @@
 #include "CommandResult.hxx"
 
 class Client;
+template<typename T> struct ConstBuffer;
 
 CommandResult
-handle_addtagid(Client &client, unsigned argc, char *argv[]);
+handle_addtagid(Client &client, ConstBuffer<const char *> args);
 
 CommandResult
-handle_cleartagid(Client &client, unsigned argc, char *argv[]);
+handle_cleartagid(Client &client, ConstBuffer<const char *> args);
 
 #endif