aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2009-11-01 15:37:16 +0100
committerMax Kellermann <max@duempel.org>2009-11-01 15:37:16 +0100
commit9bcfd3a47da540abeb2da06471cf2fa11c14db49 (patch)
tree37cfca02bd797fe16329717d036c05dee07a147a
parent451f932d80f5b695adb59e293391d17c9b996fb8 (diff)
downloadmpd-9bcfd3a47da540abeb2da06471cf2fa11c14db49.tar.gz
mpd-9bcfd3a47da540abeb2da06471cf2fa11c14db49.tar.xz
mpd-9bcfd3a47da540abeb2da06471cf2fa11c14db49.zip
text_file: allocate line buffers dynamically
Use a single GString buffer object in all functions loading the database. Enlarge it automatically for long lines. This eliminates the maximum line length for tag values. There is still an upper limit of 512 kB to prevent denial of service, but that's reasonable I guess.
-rw-r--r--Makefile.am2
-rw-r--r--NEWS1
-rw-r--r--src/database.c38
-rw-r--r--src/directory_save.c45
-rw-r--r--src/directory_save.h3
-rw-r--r--src/song_save.c31
-rw-r--r--src/song_save.h2
-rw-r--r--src/text_file.c62
-rw-r--r--src/text_file.h39
9 files changed, 164 insertions, 59 deletions
diff --git a/Makefile.am b/Makefile.am
index 4cbe6774d..d7167a4cc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -88,6 +88,7 @@ mpd_headers = \
src/input/file_input_plugin.h \
src/input/curl_input_plugin.h \
src/input/mms_input_plugin.h \
+ src/text_file.h \
src/text_input_stream.h \
src/icy_server.h \
src/icy_metadata.h \
@@ -285,6 +286,7 @@ src_mpd_SOURCES = \
src/tag_print.c \
src/tag_save.c \
src/tokenizer.c \
+ src/text_file.c \
src/text_input_stream.c \
src/strset.c \
src/uri.c \
diff --git a/NEWS b/NEWS
index 2f865d52b..59955eb61 100644
--- a/NEWS
+++ b/NEWS
@@ -53,6 +53,7 @@ ver 0.16 (20??/??/??)
* renamed option "--stdout" to "--stderr"
* removed options --create-db and --no-create-db
* state_file: save only if something has changed
+* database: eliminated maximum line length
* obey $(sysconfdir) for default mpd.conf location
* build with large file support by default
* require GLib 2.16
diff --git a/src/database.c b/src/database.c
index 5a06dda98..dc0ef3686 100644
--- a/src/database.c
+++ b/src/database.c
@@ -23,6 +23,7 @@
#include "song.h"
#include "path.h"
#include "stats.h"
+#include "text_file.h"
#include "config.h"
#include <glib.h>
@@ -256,7 +257,8 @@ db_load(GError **error)
{
FILE *fp = NULL;
struct stat st;
- char buffer[100];
+ GString *buffer = g_string_sized_new(1024);
+ char *line;
bool found_charset = false, found_version = false;
bool success;
@@ -270,50 +272,45 @@ db_load(GError **error)
g_set_error(error, db_quark(), errno,
"Failed to open database file \"%s\": %s",
database_path, strerror(errno));
+ g_string_free(buffer, true);
return false;
}
/* get initial info */
- if (!fgets(buffer, sizeof(buffer), fp)) {
- fclose(fp);
- g_set_error(error, db_quark(), 0, "Unexpected end of file");
- return false;
- }
-
- g_strchomp(buffer);
-
- if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
+ line = read_text_line(fp, buffer);
+ if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
fclose(fp);
g_set_error(error, db_quark(), 0, "Database corrupted");
+ g_string_free(buffer, true);
return false;
}
- while (fgets(buffer, sizeof(buffer), fp) &&
- !g_str_has_prefix(buffer, DIRECTORY_INFO_END)) {
- g_strchomp(buffer);
-
- if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) {
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ !g_str_has_prefix(line, DIRECTORY_INFO_END)) {
+ if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
if (found_version) {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Duplicate version line");
+ g_string_free(buffer, true);
return false;
}
found_version = true;
- } else if (g_str_has_prefix(buffer, DIRECTORY_FS_CHARSET)) {
+ } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
const char *new_charset, *old_charset;
if (found_charset) {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Duplicate charset line");
+ g_string_free(buffer, true);
return false;
}
found_charset = true;
- new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
old_charset = path_get_fs_charset();
if (old_charset != NULL
&& strcmp(new_charset, old_charset)) {
@@ -323,19 +320,22 @@ db_load(GError **error)
"\"%s\" instead of \"%s\"; "
"discarding database file",
new_charset, old_charset);
+ g_string_free(buffer, true);
return false;
}
} else {
fclose(fp);
g_set_error(error, db_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
+ g_string_free(buffer, true);
return false;
}
}
g_debug("reading DB");
- success = directory_load(fp, music_root, error);
+ success = directory_load(fp, music_root, buffer, error);
+ g_string_free(buffer, true);
while (fclose(fp) && errno == EINTR) ;
if (!success)
diff --git a/src/directory_save.c b/src/directory_save.c
index f353c7c2e..ed4f0190f 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -20,7 +20,7 @@
#include "directory_save.h"
#include "directory.h"
#include "song.h"
-#include "path.h"
+#include "text_file.h"
#include "song_save.h"
#include <assert.h>
@@ -80,10 +80,10 @@ directory_save(FILE *fp, struct directory *directory)
static struct directory *
directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
- GError **error_r)
+ GString *buffer, GError **error_r)
{
- char buffer[MPD_PATH_MAX * 2];
struct directory *directory;
+ const char *line;
bool success;
if (directory_get_child(parent, name) != NULL) {
@@ -101,19 +101,21 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
g_free(path);
}
- if (!fgets(buffer, sizeof(buffer), fp)) {
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file");
directory_free(directory);
return NULL;
}
- if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) {
+ if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
directory->mtime =
- g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1,
+ g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
NULL, 10);
- if (!fgets(buffer, sizeof(buffer), fp)) {
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file");
directory_free(directory);
@@ -121,14 +123,14 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
}
}
- if (!g_str_has_prefix(buffer, DIRECTORY_BEGIN)) {
+ if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
g_set_error(error_r, directory_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
directory_free(directory);
return NULL;
}
- success = directory_load(fp, directory, error_r);
+ success = directory_load(fp, directory, buffer, error_r);
if (!success) {
directory_free(directory);
return NULL;
@@ -138,32 +140,31 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
}
bool
-directory_load(FILE *fp, struct directory *directory, GError **error)
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error)
{
- char buffer[MPD_PATH_MAX * 2];
+ const char *line;
bool success;
- while (fgets(buffer, sizeof(buffer), fp)
- && !g_str_has_prefix(buffer, DIRECTORY_END)) {
- g_strchomp(buffer);
-
- if (g_str_has_prefix(buffer, DIRECTORY_DIR)) {
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ !g_str_has_prefix(line, DIRECTORY_END)) {
+ if (g_str_has_prefix(line, DIRECTORY_DIR)) {
struct directory *subdir =
directory_load_subdir(fp, directory,
- buffer + sizeof(DIRECTORY_DIR) - 1,
- error);
+ line + sizeof(DIRECTORY_DIR) - 1,
+ buffer, error);
if (subdir == NULL)
return false;
dirvec_add(&directory->children, subdir);
- } else if (g_str_has_prefix(buffer, SONG_BEGIN)) {
+ } else if (g_str_has_prefix(line, SONG_BEGIN)) {
success = songvec_load(fp, &directory->songs,
- directory, error);
+ directory, buffer, error);
if (!success)
return false;
} else {
g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
return false;
}
}
diff --git a/src/directory_save.h b/src/directory_save.h
index 28ec094ad..d9daa3fed 100644
--- a/src/directory_save.h
+++ b/src/directory_save.h
@@ -31,6 +31,7 @@ int
directory_save(FILE *fp, struct directory *directory);
bool
-directory_load(FILE *fp, struct directory *directory, GError **error);
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error);
#endif
diff --git a/src/song_save.c b/src/song_save.c
index b18057201..4b8409d86 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -21,8 +21,8 @@
#include "song.h"
#include "tag_save.h"
#include "directory.h"
-#include "path.h"
#include "tag.h"
+#include "text_file.h"
#include <glib.h>
@@ -117,32 +117,31 @@ parse_tag_value(char *buffer, enum tag_type *type_r)
bool
songvec_load(FILE *fp, struct songvec *sv, struct directory *parent,
- GError **error_r)
+ GString *buffer, GError **error_r)
{
- char buffer[MPD_PATH_MAX + 1024];
+ char *line;
struct song *song = NULL;
enum tag_type type;
const char *value;
- while (fgets(buffer, sizeof(buffer), fp) &&
- !g_str_has_prefix(buffer, SONG_END)) {
- g_strchomp(buffer);
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ !g_str_has_prefix(line, SONG_END)) {
- if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) {
+ if (0 == strncmp(SONG_KEY, line, strlen(SONG_KEY))) {
if (song)
commit_song(sv, song);
- song = song_file_new(buffer + strlen(SONG_KEY),
+ song = song_file_new(line + strlen(SONG_KEY),
parent);
- } else if (*buffer == 0) {
+ } else if (*line == 0) {
/* ignore empty lines (starting with '\0') */
} else if (song == NULL) {
g_set_error(error_r, song_save_quark(), 0,
"Problems reading song info");
return false;
- } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) {
+ } else if (0 == strncmp(SONG_FILE, line, strlen(SONG_FILE))) {
/* we don't need this info anymore */
- } else if ((value = parse_tag_value(buffer,
+ } else if ((value = parse_tag_value(line,
&type)) != NULL) {
if (!song->tag) {
song->tag = tag_new();
@@ -150,18 +149,18 @@ songvec_load(FILE *fp, struct songvec *sv, struct directory *parent,
}
tag_add_item(song->tag, type, value);
- } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) {
+ } else if (0 == strncmp(SONG_TIME, line, strlen(SONG_TIME))) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- song->tag->time = atoi(&(buffer[strlen(SONG_TIME)]));
- } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) {
- song->mtime = atoi(&(buffer[strlen(SONG_MTIME)]));
+ song->tag->time = atoi(&(line[strlen(SONG_TIME)]));
+ } else if (0 == strncmp(SONG_MTIME, line, strlen(SONG_MTIME))) {
+ song->mtime = atoi(&(line[strlen(SONG_MTIME)]));
} else {
g_set_error(error_r, song_save_quark(), 0,
- "unknown line in db: %s", buffer);
+ "unknown line in db: %s", line);
return false;
}
}
diff --git a/src/song_save.h b/src/song_save.h
index 36e03584e..cd62c986d 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -40,6 +40,6 @@ void songvec_save(FILE *fp, struct songvec *sv);
*/
bool
songvec_load(FILE *file, struct songvec *sv, struct directory *parent,
- GError **error_r);
+ GString *buffer, GError **error_r);
#endif
diff --git a/src/text_file.c b/src/text_file.c
new file mode 100644
index 000000000..16698fc57
--- /dev/null
+++ b/src/text_file.c
@@ -0,0 +1,62 @@
+/*
+ * 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 "text_file.h"
+
+#include <assert.h>
+#include <string.h>
+
+char *
+read_text_line(FILE *file, GString *buffer)
+{
+ enum {
+ max_length = 512 * 1024,
+ step = 1024,
+ };
+
+ gsize length = 0, i;
+ char *p;
+
+ assert(file != NULL);
+ assert(buffer != NULL);
+
+ if (buffer->allocated_len < step)
+ g_string_set_size(buffer, step);
+
+ while (buffer->len < max_length) {
+ p = fgets(buffer->str + length,
+ buffer->allocated_len - length, file);
+ if (p == NULL) {
+ if (length == 0 || ferror(file))
+ return NULL;
+ break;
+ }
+
+ i = strlen(buffer->str + length);
+ length += i;
+ if (i < step - 1 || buffer->str[length - 1] == '\n')
+ break;
+
+ g_string_set_size(buffer, length + step);
+ }
+
+ g_string_set_size(buffer, length);
+ g_strchomp(buffer->str);
+ return buffer->str;
+}
diff --git a/src/text_file.h b/src/text_file.h
new file mode 100644
index 000000000..bc5c92870
--- /dev/null
+++ b/src/text_file.h
@@ -0,0 +1,39 @@
+/*
+ * 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_TEXT_FILE_H
+#define MPD_TEXT_FILE_H
+
+#include <glib.h>
+
+#include <stdio.h>
+
+/**
+ * Reads a line from the input file, and strips trailing space. There
+ * is a reasonable maximum line length, only to prevent denial of
+ * service.
+ *
+ * @param file the source file, opened in text mode
+ * @param buffer an allocator for the buffer
+ * @return a pointer to the line, or NULL on end-of-file or error
+ */
+char *
+read_text_line(FILE *file, GString *buffer);
+
+#endif