aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am20
-rw-r--r--doc/mpdconf.example7
-rw-r--r--src/buffer2array.c133
-rw-r--r--src/buffer2array.h31
-rw-r--r--src/client.c40
-rw-r--r--src/command.c54
-rw-r--r--src/conf.c145
-rw-r--r--src/conf.h36
-rw-r--r--src/daemon.c75
-rw-r--r--src/daemon.h32
-rw-r--r--src/main.c1
-rw-r--r--src/tag_ape.c64
-rw-r--r--src/tokenizer.c167
-rw-r--r--src/tokenizer.h68
14 files changed, 533 insertions, 340 deletions
diff --git a/Makefile.am b/Makefile.am
index 5bfaae210..0367c78ed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -48,7 +48,6 @@ mpd_headers = \
src/filter/chain_filter_plugin.h \
src/filter/convert_filter_plugin.h \
src/filter/volume_filter_plugin.h \
- src/buffer2array.h \
src/command.h \
src/idle.h \
src/cmdline.h \
@@ -154,6 +153,7 @@ mpd_headers = \
src/tag_id3.h \
src/tag_print.h \
src/tag_save.h \
+ src/tokenizer.h \
src/strset.h \
src/uri.h \
src/utils.h \
@@ -181,7 +181,6 @@ src_mpd_SOURCES = \
src/notify.c \
src/audio.c \
src/audio_parser.c \
- src/buffer2array.c \
src/command.c \
src/idle.c \
src/cmdline.c \
@@ -251,6 +250,7 @@ src_mpd_SOURCES = \
src/tag_pool.c \
src/tag_print.c \
src/tag_save.c \
+ src/tokenizer.c \
src/strset.c \
src/uri.c \
src/utils.c \
@@ -649,7 +649,7 @@ test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \
test_read_conf_LDADD = $(MPD_LIBS) \
$(GLIB_LIBS)
test_read_conf_SOURCES = test/read_conf.c \
- src/conf.c src/buffer2array.c src/utils.c
+ src/conf.c src/tokenizer.c src/utils.c
test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \
$(ARCHIVE_CFLAGS) \
@@ -659,7 +659,7 @@ test_run_input_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.c \
- src/conf.c src/buffer2array.c src/utils.c \
+ src/conf.c src/tokenizer.c src/utils.c \
src/tag.c src/tag_pool.c src/tag_save.c \
$(ARCHIVE_SRC) \
$(INPUT_SRC)
@@ -674,7 +674,7 @@ test_run_decoder_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.c \
- src/conf.c src/buffer2array.c src/utils.c src/log.c \
+ src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain.c \
src/uri.c \
@@ -693,7 +693,7 @@ test_read_tags_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.c \
- src/conf.c src/buffer2array.c src/utils.c src/log.c \
+ src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain.c \
src/uri.c \
@@ -709,7 +709,7 @@ test_run_filter_LDADD = $(MPD_LIBS) \
test_run_filter_SOURCES = test/run_filter.c \
src/filter_plugin.c \
src/filter_registry.c \
- src/conf.c src/buffer2array.c src/utils.c \
+ src/conf.c src/tokenizer.c src/utils.c \
src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \
src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \
src/pcm_resample.c src/pcm_resample_fallback.c \
@@ -723,7 +723,7 @@ endif
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.c \
- src/conf.c src/buffer2array.c \
+ src/conf.c src/tokenizer.c \
src/utils.c \
src/tag.c src/tag_pool.c \
src/audio_parser.c \
@@ -747,7 +747,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
$(OUTPUT_LIBS) \
$(GLIB_LIBS)
test_run_output_SOURCES = test/run_output.c \
- src/conf.c src/buffer2array.c src/utils.c src/log.c \
+ src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/audio_parser.c \
src/timer.c \
src/tag.c src/tag_pool.c \
@@ -772,7 +772,7 @@ test_read_mixer_LDADD = $(MPD_LIBS) \
$(OUTPUT_LIBS) \
$(GLIB_LIBS)
test_read_mixer_SOURCES = test/read_mixer.c \
- src/conf.c src/buffer2array.c src/utils.c src/log.c \
+ src/conf.c src/tokenizer.c src/utils.c src/log.c \
src/mixer_control.c src/mixer_api.c \
src/filter_plugin.c \
src/filter/volume_filter_plugin.c \
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index 7574ffc87..1dce635b2 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -61,6 +61,13 @@
#
#user "nobody"
#
+# This setting specifies the group that MPD will run as. If not specified
+# primary group of user specified with "user" setting will be used (if set).
+# This is useful if MPD needs to be a member of group such as "audio" to
+# have permission to use sound card.
+#
+#group "nogroup"
+#
# This setting sets the address for the daemon to listen on. Careful attention
# should be paid if this is assigned to anything other then the default, any.
# This setting can deny access to control of the daemon.
diff --git a/src/buffer2array.c b/src/buffer2array.c
deleted file mode 100644
index b6029d754..000000000
--- a/src/buffer2array.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "buffer2array.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-int buffer2array(char *buffer, char *array[], const int max)
-{
- int i = 0;
- char *c = buffer;
-
- while (*c != '\0' && i < max) {
- if (*c == '\"') {
- array[i++] = ++c;
- while (*c != '\0') {
- if (*c == '\"') {
- *(c++) = '\0';
- break;
- }
- else if (*(c++) == '\\' && *c != '\0') {
- memmove(c - 1, c, strlen(c) + 1);
- }
- }
- } else {
- c = g_strchug(c);
- if (*c == '\0')
- return i;
-
- array[i++] = c++;
-
- while (!g_ascii_isspace(*c) && *c != '\0')
- ++c;
- }
- if (*c == '\0')
- return i;
- *(c++) = '\0';
-
- c = g_strchug(c);
- }
- return i;
-}
-
-#ifdef UNIT_TEST
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-int main()
-{
- char *a[4] = { NULL };
- char *b;
- int max;
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir\\\\name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir\\name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\" x", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- return 0;
-}
-
-#endif
diff --git a/src/buffer2array.h b/src/buffer2array.h
deleted file mode 100644
index bed23a29f..000000000
--- a/src/buffer2array.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_BUFFER_2_ARRAY_H
-#define MPD_BUFFER_2_ARRAY_H
-
-/* tokenizes up to max elements in buffer (a null-terminated string) and
- * stores the result in array (which must be capable of holding up to
- * max elements). Tokenization is based on C string quoting rules.
- * The arguments buffer and array are modified.
- * Returns the number of elements tokenized.
- */
-int buffer2array(char *buffer, char *array[], const int max);
-
-#endif
diff --git a/src/client.c b/src/client.c
index 6a256998f..7fd2cce06 100644
--- a/src/client.c
+++ b/src/client.c
@@ -284,9 +284,10 @@ void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
g_free(remote);
}
-static int client_process_line(struct client *client, char *line)
+static enum command_return
+client_process_line(struct client *client, char *line)
{
- int ret = 1;
+ enum command_return ret;
if (strcmp(line, "noidle") == 0) {
if (client->idle_waiting) {
@@ -300,7 +301,7 @@ static int client_process_line(struct client *client, char *line)
has already received the full idle response from
client_idle_notify(), which he can now evaluate */
- return 0;
+ return COMMAND_RETURN_OK;
} else if (client->idle_waiting) {
/* during idle mode, clients must not send anything
except "noidle" */
@@ -329,7 +330,7 @@ static int client_process_line(struct client *client, char *line)
client_is_expired(client))
return COMMAND_RETURN_CLOSE;
- if (ret == 0)
+ if (ret == COMMAND_RETURN_OK)
command_success(client);
client_write_output(client);
@@ -347,16 +348,18 @@ static int client_process_line(struct client *client, char *line)
(unsigned long)client->cmd_list_size,
(unsigned long)client_max_command_list_size);
return COMMAND_RETURN_CLOSE;
- } else
- new_cmd_list_ptr(client, line);
+ }
+
+ new_cmd_list_ptr(client, line);
+ ret = COMMAND_RETURN_OK;
}
} else {
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
client->cmd_list_OK = 0;
- ret = 1;
+ ret = COMMAND_RETURN_OK;
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
client->cmd_list_OK = 1;
- ret = 1;
+ ret = COMMAND_RETURN_OK;
} else {
g_debug("[%u] process command \"%s\"",
client->num, line);
@@ -368,7 +371,7 @@ static int client_process_line(struct client *client, char *line)
client_is_expired(client))
return COMMAND_RETURN_CLOSE;
- if (ret == 0)
+ if (ret == COMMAND_RETURN_OK)
command_success(client);
client_write_output(client);
@@ -399,17 +402,17 @@ client_read_line(struct client *client)
return g_strchomp(line);
}
-static int client_input_received(struct client *client, size_t bytesRead)
+static enum command_return
+client_input_received(struct client *client, size_t bytesRead)
{
char *line;
- int ret;
fifo_buffer_append(client->input, bytesRead);
/* process all lines */
while ((line = client_read_line(client)) != NULL) {
- ret = client_process_line(client, line);
+ enum command_return ret = client_process_line(client, line);
g_free(line);
if (ret == COMMAND_RETURN_KILL ||
@@ -419,10 +422,11 @@ static int client_input_received(struct client *client, size_t bytesRead)
return COMMAND_RETURN_CLOSE;
}
- return 0;
+ return COMMAND_RETURN_OK;
}
-static int client_read(struct client *client)
+static enum command_return
+client_read(struct client *client)
{
char *p;
size_t max_length;
@@ -447,7 +451,7 @@ static int client_read(struct client *client)
case G_IO_STATUS_AGAIN:
/* try again later, after select() */
- return 0;
+ return COMMAND_RETURN_OK;
case G_IO_STATUS_EOF:
/* peer disconnected */
@@ -475,7 +479,7 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source,
gpointer data)
{
struct client *client = data;
- int ret;
+ enum command_return ret;
assert(!client_is_expired(client));
@@ -488,6 +492,10 @@ client_in_event(G_GNUC_UNUSED GIOChannel *source,
ret = client_read(client);
switch (ret) {
+ case COMMAND_RETURN_OK:
+ case COMMAND_RETURN_ERROR:
+ break;
+
case COMMAND_RETURN_KILL:
client_close(client);
g_main_loop_quit(main_loop);
diff --git a/src/command.c b/src/command.c
index 58b67d8ce..b2cf24975 100644
--- a/src/command.c
+++ b/src/command.c
@@ -32,7 +32,7 @@
#include "volume.h"
#include "stats.h"
#include "permission.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "stored_playlist.h"
#include "ack.h"
#include "output_command.h"
@@ -1878,15 +1878,61 @@ command_checked_lookup(struct client *client, unsigned permission,
}
enum command_return
-command_process(struct client *client, char *commandString)
+command_process(struct client *client, char *line)
{
+ GError *error = NULL;
int argc;
char *argv[COMMAND_ARGV_MAX] = { NULL };
const struct command *cmd;
enum command_return ret = COMMAND_RETURN_ERROR;
- if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
- return COMMAND_RETURN_OK;
+ /* get the command name (first word on the line) */
+
+ argv[0] = tokenizer_next_word(&line, &error);
+ if (argv[0] == NULL) {
+ current_command = "";
+ if (*line == 0)
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "No command given");
+ else {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "%s", error->message);
+ g_error_free(error);
+ }
+ current_command = NULL;
+
+ return COMMAND_RETURN_ERROR;
+ }
+
+ argc = 1;
+
+ /* now parse the arguments (quoted or unquoted) */
+
+ while (argc < (int)G_N_ELEMENTS(argv) &&
+ (argv[argc] =
+ tokenizer_next_word_or_string(&line, &error)) != NULL)
+ ++argc;
+
+ /* some error checks; we have to set current_command because
+ command_error() expects it to be set */
+
+ current_command = argv[0];
+
+ if (argc >= (int)G_N_ELEMENTS(argv)) {
+ command_error(client, ACK_ERROR_ARG, "Too many arguments");
+ current_command = NULL;
+ return COMMAND_RETURN_ERROR;
+ }
+
+ if (*line != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "%s", error->message);
+ current_command = NULL;
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* look up and invoke the command handler */
cmd = command_checked_lookup(client, client_get_permission(client),
argc, argv);
diff --git a/src/conf.c b/src/conf.c
index 777cbe6ed..ce80100f0 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -19,7 +19,7 @@
#include "conf.h"
#include "utils.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "path.h"
#include <glib.h>
@@ -36,10 +36,6 @@
#define MAX_STRING_SIZE MPD_PATH_MAX+80
#define CONF_COMMENT '#'
-#define CONF_BLOCK_BEGIN "{"
-#define CONF_BLOCK_END "}"
-
-#define CONF_LINE_TOKEN_MAX 3
struct config_entry {
const char *const name;
@@ -61,6 +57,7 @@ static struct config_entry config_entries[] = {
{ .name = CONF_PID_FILE, false, false },
{ .name = CONF_STATE_FILE, false, false },
{ .name = CONF_USER, false, false },
+ { .name = CONF_GROUP, false, false },
{ .name = CONF_BIND_TO_ADDRESS, true, false },
{ .name = CONF_PORT, false, false },
{ .name = CONF_LOG_LEVEL, false, false },
@@ -249,60 +246,59 @@ config_read_block(FILE *fp, int *count, char *string)
{
struct config_param *ret = config_new_param(NULL, *count);
- int i;
- int numberOfArgs;
- int argsMinusComment;
+ while (true) {
+ char *line;
+ const char *name, *value;
+ GError *error = NULL;
- while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ line = fgets(string, MAX_STRING_SIZE, fp);
+ if (line == NULL)
+ g_error("Expected '}' before end-of-file");
(*count)++;
+ line = g_strchug(line);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
-
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
- }
+ if (*line == '}') {
+ /* end of this block; return from the function
+ (and from this "while" loop) */
- argsMinusComment = i;
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT)
+ g_error("line %i: Unknown tokens after '}'",
+ *count);
- if (0 == argsMinusComment) {
- continue;
+ return ret;
}
- if (1 == argsMinusComment &&
- 0 == strcmp(array[0], CONF_BLOCK_END)) {
- break;
- }
+ /* parse name and value */
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", *count, string);
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ g_error("line %i: %s", *count, error->message);
}
- if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[0], CONF_BLOCK_END) ||
- 0 == strcmp(array[1], CONF_BLOCK_END)) {
- g_error("improperly formatted config file at line %i: %s "
- "in block beginning at line %i\n",
- *count, string, ret->line);
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ if (*line == 0)
+ g_error("line %i: Value missing", *count);
+ else
+ g_error("line %i: %s", *count, error->message);
}
- config_add_block_param(ret, array[0], array[1], *count);
- }
+ if (*line != 0 && *line != CONF_COMMENT)
+ g_error("line %i: Unknown tokens after value", *count);
- return ret;
+ config_add_block_param(ret, name, value, *count);
+ }
}
void config_read_file(const char *file)
{
FILE *fp;
char string[MAX_STRING_SIZE + 1];
- int i;
- int numberOfArgs;
- int argsMinusComment;
int count = 0;
struct config_entry *entry;
struct config_param *param;
@@ -315,48 +311,73 @@ void config_read_file(const char *file)
}
while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ char *line;
+ const char *name, *value;
+ GError *error = NULL;
count++;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
-
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
- }
+ line = g_strchug(string);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
- argsMinusComment = i;
+ /* the first token in each line is the name, followed
+ by either the value or '{' */
- if (0 == argsMinusComment) {
- continue;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ g_error("line %i: %s", count, error->message);
}
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", count, string);
- }
+ /* get the definition of that option, and check the
+ "repeatable" flag */
- entry = config_entry_get(array[0]);
+ entry = config_entry_get(name);
if (entry == NULL)
g_error("unrecognized parameter in config file at "
- "line %i: %s\n", count, string);
+ "line %i: %s\n", count, name);
if (entry->params != NULL && !entry->repeatable) {
param = entry->params->data;
g_error("config parameter \"%s\" is first defined on "
"line %i and redefined on line %i\n",
- array[0], param->line, count);
+ name, param->line, count);
}
+ /* now parse the block or the value */
+
if (entry->block) {
- if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
- g_error("improperly formatted config file at "
- "line %i: %s\n", count, string);
- }
+ /* it's a block, call config_read_block() */
+
+ if (*line != '{')
+ g_error("line %i: '{' expected", count);
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT)
+ g_error("line %i: Unknown tokens after '{'",
+ count);
+
param = config_read_block(fp, &count, string);
- } else
- param = config_new_param(array[1], count);
+ } else {
+ /* a string value */
+
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ if (*line == 0)
+ g_error("line %i: Value missing",
+ count);
+ else
+ g_error("line %i: %s", count,
+ error->message);
+ }
+
+ if (*line != 0 && *line != CONF_COMMENT)
+ g_error("line %i: Unknown tokens after value",
+ count);
+
+ param = config_new_param(value, count);
+ }
entry->params = g_slist_append(entry->params, param);
}
diff --git a/src/conf.h b/src/conf.h
index 669542167..4eb9db63c 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -34,6 +34,7 @@
#define CONF_PID_FILE "pid_file"
#define CONF_STATE_FILE "state_file"
#define CONF_USER "user"
+#define CONF_GROUP "group"
#define CONF_BIND_TO_ADDRESS "bind_to_address"
#define CONF_PORT "port"
#define CONF_LOG_LEVEL "log_level"
@@ -101,7 +102,6 @@ struct config_param {
* A GQuark for GError instances, resulting from malformed
* configuration.
*/
-G_GNUC_CONST
static inline GQuark
config_quark(void)
{
@@ -121,18 +121,25 @@ void config_read_file(const char *file);
/* don't free the returned value
set _last_ to NULL to get first entry */
-G_GNUC_CONST
+G_GNUC_PURE
struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
-G_GNUC_CONST
+G_GNUC_PURE
static inline struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
}
-G_GNUC_CONST
+/* Note on G_GNUC_PURE: Some of the functions declared pure are not
+ really pure in strict sense. They have side effect such that they
+ validate parameter's value and signal an error if it's invalid.
+ However, if the argument was already validated or we don't care
+ about the argument at all, this may be ignored so in the end, we
+ should be fine with calling those functions pure. */
+
+G_GNUC_PURE
const char *
config_get_string(const char *name, const char *default_value);
@@ -141,27 +148,31 @@ config_get_string(const char *name, const char *default_value);
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
*/
-G_GNUC_CONST
+/* We lie here really. This function is not pure as it has side
+ effects -- it parse the value and creates new string freeing
+ previous one. However, because this works the very same way each
+ time (ie. from the outside it appears as if function had no side
+ effects) we should be in the clear declaring it pure. */
+G_GNUC_PURE
const char *
config_get_path(const char *name);
-G_GNUC_CONST
+G_GNUC_PURE
unsigned
config_get_positive(const char *name, unsigned default_value);
-G_GNUC_CONST
+G_GNUC_PURE
struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
-G_GNUC_CONST
+G_GNUC_PURE
bool config_get_bool(const char *name, bool default_value);
-G_GNUC_CONST
+G_GNUC_PURE
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
-G_GNUC_CONST
static inline char *
config_dup_block_string(const struct config_param *param, const char *name,
const char *default_value)
@@ -169,17 +180,16 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
-G_GNUC_CONST
+G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
-G_GNUC_CONST
+G_GNUC_PURE
bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
-G_GNUC_CONST
struct config_param *
config_new_param(const char *value, int line);
diff --git a/src/daemon.c b/src/daemon.c
index 33b2953a9..43d16bc9b 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -45,20 +45,21 @@
static char *user_name;
/** the Unix user id which MPD runs as */
-static uid_t user_uid;
+static uid_t user_uid = (uid_t)-1;
/** the Unix group id which MPD runs as */
-static gid_t user_gid;
+static gid_t user_gid = (pid_t)-1;
/** the absolute path of the pidfile */
static char *pidfile;
-#endif
+/* whether "group" conf. option was given */
+static bool had_group = false;
+
void
daemonize_kill(void)
{
-#ifndef WIN32
FILE *fp;
int pid, ret;
@@ -82,11 +83,10 @@ daemonize_kill(void)
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
-#else
- g_error("--kill is not available on WIN32");
-#endif
}
+#endif
+
void
daemonize_close_stdin(void)
{
@@ -100,23 +100,27 @@ daemonize_close_stdin(void)
}
}
+#ifndef WIN32
+
void
daemonize_set_user(void)
{
-#ifndef WIN32
if (user_name == NULL)
return;
- /* get uid */
- if (setgid(user_gid) == -1) {
- g_error("cannot setgid for user \"%s\": %s",
- user_name, g_strerror(errno));
+ /* set gid */
+ if (user_gid != (gid_t)-1 && user_gid != getgid()) {
+ if (setgid(user_gid) == -1) {
+ g_error("cannot setgid to %d: %s",
+ (int)user_gid, g_strerror(errno));
+ }
}
+
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
- if (initgroups(user_name, user_gid) == -1) {
+ if (!had_group && initgroups(user_name, user_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\": %s",
user_name, g_strerror(errno));
@@ -124,14 +128,13 @@ daemonize_set_user(void)
#endif
/* set uid */
- if (setuid(user_uid) == -1) {
+ if (user_uid != (uid_t)-1 && user_uid != getuid() &&
+ setuid(user_uid) == -1) {
g_error("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
}
-#endif
}
-#ifndef G_OS_WIN32
static void
daemonize_detach(void)
{
@@ -162,12 +165,10 @@ daemonize_detach(void)
g_debug("daemonized!");
}
-#endif
void
daemonize(bool detach)
{
-#ifndef WIN32
FILE *fp = NULL;
if (pidfile != NULL) {
@@ -189,47 +190,45 @@ daemonize(bool detach)
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
}
-#else
- /* no daemonization on WIN32 */
- (void)detach;
-#endif
}
void
-daemonize_init(const char *user, const char *_pidfile)
+daemonize_init(const char *user, const char *group, const char *_pidfile)
{
-#ifndef WIN32
- if (user != NULL && strcmp(user, g_get_user_name()) != 0) {
- struct passwd *pwd;
-
- user_name = g_strdup(user);
-
- pwd = getpwnam(user_name);
- if (pwd == NULL)
- g_error("no such user \"%s\"", user_name);
+ if (user) {
+ struct passwd *pwd = getpwnam(user);
+ if (!pwd)
+ g_error("no such user \"%s\"", user);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
+ user_name = g_strdup(user);
+
/* this is needed by libs such as arts */
g_setenv("HOME", pwd->pw_dir, true);
}
+ if (group) {
+ struct group *grp = grp = getgrnam(group);
+ if (!grp)
+ g_error("no such group \"%s\"", group);
+ user_gid = grp->gr_gid;
+ had_group = true;
+ }
+
+
pidfile = g_strdup(_pidfile);
-#else
- (void)user;
- (void)_pidfile;
-#endif
}
void
daemonize_finish(void)
{
-#ifndef WIN32
if (pidfile != NULL)
unlink(pidfile);
g_free(user_name);
g_free(pidfile);
-#endif
}
+
+#endif
diff --git a/src/daemon.h b/src/daemon.h
index 5b3f9a7dc..1332eaf48 100644
--- a/src/daemon.h
+++ b/src/daemon.h
@@ -22,18 +22,36 @@
#include <stdbool.h>
+#ifndef WIN32
void
-daemonize_init(const char *user, const char *pidfile);
+daemonize_init(const char *user, const char *group, const char *pidfile);
+#else
+static inline void
+daemonize_init(const char *user, const char *group, const char *pidfile)
+{ (void)user; (void)group; (void)pidfile; }
+#endif
+#ifndef WIN32
void
daemonize_finish(void);
+#else
+static inline void
+daemonize_finish(void)
+{ /* nop */ }
+#endif
/**
* Kill the MPD which is currently running, pid determined from the
* pid file.
*/
+#ifndef WIN32
void
daemonize_kill(void);
+#else
+static inline void
+daemonize_kill(void)
+{ g_error("--kill is not available on WIN32"); }
+#endif
/**
* Close stdin (fd 0) and re-open it as /dev/null.
@@ -44,10 +62,22 @@ daemonize_close_stdin(void);
/**
* Change to the configured Unix user.
*/
+#ifndef WIN32
void
daemonize_set_user(void);
+#else
+static inline void
+daemonize_set_user(void)
+{ /* nop */ }
+#endif
+#ifndef WIN32
void
daemonize(bool detach);
+#else
+static inline void
+daemonize(bool detach)
+{ (void)detach; }
+#endif
#endif
diff --git a/src/main.c b/src/main.c
index 3d0aaabc9..54b31c1f8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -94,6 +94,7 @@ static void
glue_daemonize_init(const struct options *options)
{
daemonize_init(config_get_string(CONF_USER, NULL),
+ config_get_string(CONF_GROUP, NULL),
config_get_path(CONF_PID_FILE));
if (options->kill)
diff --git a/src/tag_ape.c b/src/tag_ape.c
index d1249fcb2..4c3f4cf16 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -24,6 +24,36 @@
#include <stdio.h>
+static const char *const ape_tag_names[] = {
+ [TAG_ITEM_TITLE] = "title",
+ [TAG_ITEM_ARTIST] = "artist",
+ [TAG_ITEM_ALBUM] = "album",
+ [TAG_ITEM_COMMENT] = "comment",
+ [TAG_ITEM_GENRE] = "genre",
+ [TAG_ITEM_TRACK] = "track",
+ [TAG_ITEM_DATE] = "year"
+};
+
+static struct tag *
+tag_ape_import_item(struct tag *tag, unsigned long flags,
+ const char *key, const char *value, size_t value_length)
+{
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return tag;
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(ape_tag_names); i++) {
+ if (ape_tag_names[i] != NULL &&
+ g_ascii_strcasecmp(key, ape_tag_names[i]) == 0) {
+ if (tag == NULL)
+ tag = tag_new();
+ tag_add_item_n(tag, i, value, value_length);
+ }
+ }
+
+ return tag;
+}
+
struct tag *
tag_ape_load(const char *file)
{
@@ -35,7 +65,6 @@ tag_ape_load(const char *file)
size_t tagLen;
size_t size;
unsigned long flags;
- int i;
char *key;
struct {
@@ -47,26 +76,6 @@ tag_ape_load(const char *file)
unsigned char reserved[8];
} footer;
- const char *apeItems[7] = {
- "title",
- "artist",
- "album",
- "comment",
- "genre",
- "track",
- "year"
- };
-
- int tagItems[7] = {
- TAG_ITEM_TITLE,
- TAG_ITEM_ARTIST,
- TAG_ITEM_ALBUM,
- TAG_ITEM_COMMENT,
- TAG_ITEM_GENRE,
- TAG_ITEM_TRACK,
- TAG_ITEM_DATE,
- };
-
fp = fopen(file, "r");
if (!fp)
return NULL;
@@ -123,17 +132,8 @@ tag_ape_load(const char *file)
if (tagLen < size)
goto fail;
- /* we only care about utf-8 text tags */
- if (!(flags & (0x3 << 1))) {
- for (i = 0; i < 7; i++) {
- if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
- if (!ret)
- ret = tag_new();
- tag_add_item_n(ret, tagItems[i],
- p, size);
- }
- }
- }
+ ret = tag_ape_import_item(ret, flags, key, p, size);
+
p += size;
tagLen -= size;
}
diff --git a/src/tokenizer.c b/src/tokenizer.c
new file mode 100644
index 000000000..635d507df
--- /dev/null
+++ b/src/tokenizer.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tokenizer.h"
+
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+G_GNUC_CONST
+static GQuark
+tokenizer_quark(void)
+{
+ return g_quark_from_static_string("tokenizer");
+}
+
+static inline bool
+valid_word_first_char(char ch)
+{
+ return g_ascii_isalpha(ch);
+}
+
+static inline bool
+valid_word_char(char ch)
+{
+ return g_ascii_isalnum(ch) || ch == '_';
+}
+
+char *
+tokenizer_next_word(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_word_first_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Letter expected");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_word_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid word character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+char *
+tokenizer_next_string(char **input_p, GError **error_r)
+{
+ char *word, *dest, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = dest = input = *input_p;
+
+ if (*input == 0)
+ /* end of line */
+ return NULL;
+
+ /* check for the opening " */
+
+ if (*input != '"') {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "'\"' expected");
+ return NULL;
+ }
+
+ ++input;
+
+ /* copy all characters */
+
+ while (*input != '"') {
+ if (*input == '\\')
+ /* the backslash escapes the following
+ character */
+ ++input;
+
+ if (*input == 0) {
+ /* return input-1 so the caller can see the
+ difference between "end of line" and
+ "error" */
+ *input_p = input - 1;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Missing closing '\"'");
+ return NULL;
+ }
+
+ /* copy one character */
+ *dest++ = *input++;
+ }
+
+ /* the following character must be a whitespace (or end of
+ line) */
+
+ ++input;
+ if (*input != 0 && !g_ascii_isspace(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Space expected after closing '\"'");
+ return NULL;
+ }
+
+ /* finish the string and return it */
+
+ *dest = 0;
+ *input_p = g_strchug(input);
+ return word;
+}
+
+char *
+tokenizer_next_word_or_string(char **input_p, GError **error_r)
+{
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ if (**input_p == '"')
+ return tokenizer_next_string(input_p, error_r);
+ else
+ return tokenizer_next_word(input_p, error_r);
+}
diff --git a/src/tokenizer.h b/src/tokenizer.h
new file mode 100644
index 000000000..e0238f0af
--- /dev/null
+++ b/src/tokenizer.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TOKENIZER_H
+#define MPD_TOKENIZER_H
+
+#include <glib.h>
+
+/**
+ * Reads the next word from the input string. This function modifies
+ * the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_word(char **input_p, GError **error_r);
+
+/**
+ * Reads the next quoted string from the input string. A backslash
+ * escapes the following character. This function modifies the input
+ * string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_string(char **input_p, GError **error_r);
+
+/**
+ * Reads the next word or quoted string from the input. This is a
+ * wrapper for tokenizer_next_word() and tokenizer_next_string().
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_word_or_string(char **input_p, GError **error_r);
+
+#endif