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.
This commit is contained in:
Max Kellermann 2009-11-01 15:37:16 +01:00
parent 451f932d80
commit 9bcfd3a47d
9 changed files with 164 additions and 59 deletions

View File

@ -88,6 +88,7 @@ mpd_headers = \
src/input/file_input_plugin.h \ src/input/file_input_plugin.h \
src/input/curl_input_plugin.h \ src/input/curl_input_plugin.h \
src/input/mms_input_plugin.h \ src/input/mms_input_plugin.h \
src/text_file.h \
src/text_input_stream.h \ src/text_input_stream.h \
src/icy_server.h \ src/icy_server.h \
src/icy_metadata.h \ src/icy_metadata.h \
@ -285,6 +286,7 @@ src_mpd_SOURCES = \
src/tag_print.c \ src/tag_print.c \
src/tag_save.c \ src/tag_save.c \
src/tokenizer.c \ src/tokenizer.c \
src/text_file.c \
src/text_input_stream.c \ src/text_input_stream.c \
src/strset.c \ src/strset.c \
src/uri.c \ src/uri.c \

1
NEWS
View File

@ -53,6 +53,7 @@ ver 0.16 (20??/??/??)
* renamed option "--stdout" to "--stderr" * renamed option "--stdout" to "--stderr"
* removed options --create-db and --no-create-db * removed options --create-db and --no-create-db
* state_file: save only if something has changed * state_file: save only if something has changed
* database: eliminated maximum line length
* obey $(sysconfdir) for default mpd.conf location * obey $(sysconfdir) for default mpd.conf location
* build with large file support by default * build with large file support by default
* require GLib 2.16 * require GLib 2.16

View File

@ -23,6 +23,7 @@
#include "song.h" #include "song.h"
#include "path.h" #include "path.h"
#include "stats.h" #include "stats.h"
#include "text_file.h"
#include "config.h" #include "config.h"
#include <glib.h> #include <glib.h>
@ -256,7 +257,8 @@ db_load(GError **error)
{ {
FILE *fp = NULL; FILE *fp = NULL;
struct stat st; struct stat st;
char buffer[100]; GString *buffer = g_string_sized_new(1024);
char *line;
bool found_charset = false, found_version = false; bool found_charset = false, found_version = false;
bool success; bool success;
@ -270,50 +272,45 @@ db_load(GError **error)
g_set_error(error, db_quark(), errno, g_set_error(error, db_quark(), errno,
"Failed to open database file \"%s\": %s", "Failed to open database file \"%s\": %s",
database_path, strerror(errno)); database_path, strerror(errno));
g_string_free(buffer, true);
return false; return false;
} }
/* get initial info */ /* get initial info */
if (!fgets(buffer, sizeof(buffer), fp)) { line = read_text_line(fp, buffer);
fclose(fp); if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
g_set_error(error, db_quark(), 0, "Unexpected end of file");
return false;
}
g_strchomp(buffer);
if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
fclose(fp); fclose(fp);
g_set_error(error, db_quark(), 0, "Database corrupted"); g_set_error(error, db_quark(), 0, "Database corrupted");
g_string_free(buffer, true);
return false; return false;
} }
while (fgets(buffer, sizeof(buffer), fp) && while ((line = read_text_line(fp, buffer)) != NULL &&
!g_str_has_prefix(buffer, DIRECTORY_INFO_END)) { !g_str_has_prefix(line, DIRECTORY_INFO_END)) {
g_strchomp(buffer); if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) {
if (found_version) { if (found_version) {
fclose(fp); fclose(fp);
g_set_error(error, db_quark(), 0, g_set_error(error, db_quark(), 0,
"Duplicate version line"); "Duplicate version line");
g_string_free(buffer, true);
return false; return false;
} }
found_version = true; 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; const char *new_charset, *old_charset;
if (found_charset) { if (found_charset) {
fclose(fp); fclose(fp);
g_set_error(error, db_quark(), 0, g_set_error(error, db_quark(), 0,
"Duplicate charset line"); "Duplicate charset line");
g_string_free(buffer, true);
return false; return false;
} }
found_charset = true; found_charset = true;
new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]); new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
old_charset = path_get_fs_charset(); old_charset = path_get_fs_charset();
if (old_charset != NULL if (old_charset != NULL
&& strcmp(new_charset, old_charset)) { && strcmp(new_charset, old_charset)) {
@ -323,19 +320,22 @@ db_load(GError **error)
"\"%s\" instead of \"%s\"; " "\"%s\" instead of \"%s\"; "
"discarding database file", "discarding database file",
new_charset, old_charset); new_charset, old_charset);
g_string_free(buffer, true);
return false; return false;
} }
} else { } else {
fclose(fp); fclose(fp);
g_set_error(error, db_quark(), 0, g_set_error(error, db_quark(), 0,
"Malformed line: %s", buffer); "Malformed line: %s", line);
g_string_free(buffer, true);
return false; return false;
} }
} }
g_debug("reading DB"); 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) ; while (fclose(fp) && errno == EINTR) ;
if (!success) if (!success)

View File

@ -20,7 +20,7 @@
#include "directory_save.h" #include "directory_save.h"
#include "directory.h" #include "directory.h"
#include "song.h" #include "song.h"
#include "path.h" #include "text_file.h"
#include "song_save.h" #include "song_save.h"
#include <assert.h> #include <assert.h>
@ -80,10 +80,10 @@ directory_save(FILE *fp, struct directory *directory)
static struct directory * static struct directory *
directory_load_subdir(FILE *fp, struct directory *parent, const char *name, 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; struct directory *directory;
const char *line;
bool success; bool success;
if (directory_get_child(parent, name) != NULL) { 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); 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, g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file"); "Unexpected end of file");
directory_free(directory); directory_free(directory);
return NULL; return NULL;
} }
if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) { if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
directory->mtime = directory->mtime =
g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1, g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
NULL, 10); 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, g_set_error(error_r, directory_quark(), 0,
"Unexpected end of file"); "Unexpected end of file");
directory_free(directory); 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, g_set_error(error_r, directory_quark(), 0,
"Malformed line: %s", buffer); "Malformed line: %s", line);
directory_free(directory); directory_free(directory);
return NULL; return NULL;
} }
success = directory_load(fp, directory, error_r); success = directory_load(fp, directory, buffer, error_r);
if (!success) { if (!success) {
directory_free(directory); directory_free(directory);
return NULL; return NULL;
@ -138,32 +140,31 @@ directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
} }
bool 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; bool success;
while (fgets(buffer, sizeof(buffer), fp) while ((line = read_text_line(fp, buffer)) != NULL &&
&& !g_str_has_prefix(buffer, DIRECTORY_END)) { !g_str_has_prefix(line, DIRECTORY_END)) {
g_strchomp(buffer); if (g_str_has_prefix(line, DIRECTORY_DIR)) {
if (g_str_has_prefix(buffer, DIRECTORY_DIR)) {
struct directory *subdir = struct directory *subdir =
directory_load_subdir(fp, directory, directory_load_subdir(fp, directory,
buffer + sizeof(DIRECTORY_DIR) - 1, line + sizeof(DIRECTORY_DIR) - 1,
error); buffer, error);
if (subdir == NULL) if (subdir == NULL)
return false; return false;
dirvec_add(&directory->children, subdir); 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, success = songvec_load(fp, &directory->songs,
directory, error); directory, buffer, error);
if (!success) if (!success)
return false; return false;
} else { } else {
g_set_error(error, directory_quark(), 0, g_set_error(error, directory_quark(), 0,
"Malformed line: %s", buffer); "Malformed line: %s", line);
return false; return false;
} }
} }

View File

@ -31,6 +31,7 @@ int
directory_save(FILE *fp, struct directory *directory); directory_save(FILE *fp, struct directory *directory);
bool bool
directory_load(FILE *fp, struct directory *directory, GError **error); directory_load(FILE *fp, struct directory *directory,
GString *buffer, GError **error);
#endif #endif

View File

@ -21,8 +21,8 @@
#include "song.h" #include "song.h"
#include "tag_save.h" #include "tag_save.h"
#include "directory.h" #include "directory.h"
#include "path.h"
#include "tag.h" #include "tag.h"
#include "text_file.h"
#include <glib.h> #include <glib.h>
@ -117,32 +117,31 @@ parse_tag_value(char *buffer, enum tag_type *type_r)
bool bool
songvec_load(FILE *fp, struct songvec *sv, struct directory *parent, 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; struct song *song = NULL;
enum tag_type type; enum tag_type type;
const char *value; const char *value;
while (fgets(buffer, sizeof(buffer), fp) && while ((line = read_text_line(fp, buffer)) != NULL &&
!g_str_has_prefix(buffer, SONG_END)) { !g_str_has_prefix(line, SONG_END)) {
g_strchomp(buffer);
if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) { if (0 == strncmp(SONG_KEY, line, strlen(SONG_KEY))) {
if (song) if (song)
commit_song(sv, song); commit_song(sv, song);
song = song_file_new(buffer + strlen(SONG_KEY), song = song_file_new(line + strlen(SONG_KEY),
parent); parent);
} else if (*buffer == 0) { } else if (*line == 0) {
/* ignore empty lines (starting with '\0') */ /* ignore empty lines (starting with '\0') */
} else if (song == NULL) { } else if (song == NULL) {
g_set_error(error_r, song_save_quark(), 0, g_set_error(error_r, song_save_quark(), 0,
"Problems reading song info"); "Problems reading song info");
return false; 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 */ /* we don't need this info anymore */
} else if ((value = parse_tag_value(buffer, } else if ((value = parse_tag_value(line,
&type)) != NULL) { &type)) != NULL) {
if (!song->tag) { if (!song->tag) {
song->tag = tag_new(); 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); 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) { if (!song->tag) {
song->tag = tag_new(); song->tag = tag_new();
tag_begin_add(song->tag); tag_begin_add(song->tag);
} }
song->tag->time = atoi(&(buffer[strlen(SONG_TIME)])); song->tag->time = atoi(&(line[strlen(SONG_TIME)]));
} else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) { } else if (0 == strncmp(SONG_MTIME, line, strlen(SONG_MTIME))) {
song->mtime = atoi(&(buffer[strlen(SONG_MTIME)])); song->mtime = atoi(&(line[strlen(SONG_MTIME)]));
} else { } else {
g_set_error(error_r, song_save_quark(), 0, g_set_error(error_r, song_save_quark(), 0,
"unknown line in db: %s", buffer); "unknown line in db: %s", line);
return false; return false;
} }
} }

View File

@ -40,6 +40,6 @@ void songvec_save(FILE *fp, struct songvec *sv);
*/ */
bool bool
songvec_load(FILE *file, struct songvec *sv, struct directory *parent, songvec_load(FILE *file, struct songvec *sv, struct directory *parent,
GError **error_r); GString *buffer, GError **error_r);
#endif #endif

62
src/text_file.c Normal file
View File

@ -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;
}

39
src/text_file.h Normal file
View File

@ -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