diff options
-rw-r--r-- | Makefile.am | 20 | ||||
-rw-r--r-- | doc/mpdconf.example | 7 | ||||
-rw-r--r-- | src/buffer2array.c | 133 | ||||
-rw-r--r-- | src/buffer2array.h | 31 | ||||
-rw-r--r-- | src/client.c | 40 | ||||
-rw-r--r-- | src/command.c | 54 | ||||
-rw-r--r-- | src/conf.c | 145 | ||||
-rw-r--r-- | src/conf.h | 36 | ||||
-rw-r--r-- | src/daemon.c | 75 | ||||
-rw-r--r-- | src/daemon.h | 32 | ||||
-rw-r--r-- | src/main.c | 1 | ||||
-rw-r--r-- | src/tag_ape.c | 64 | ||||
-rw-r--r-- | src/tokenizer.c | 167 | ||||
-rw-r--r-- | src/tokenizer.h | 68 |
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 |