diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx
index 81aeae2cd..336a6d0e0 100644
--- a/src/PlaylistDatabase.cxx
+++ b/src/PlaylistDatabase.cxx
@@ -21,6 +21,7 @@
 #include "PlaylistDatabase.hxx"
 #include "db/PlaylistVector.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "util/StringUtil.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
@@ -31,13 +32,13 @@
 static constexpr Domain playlist_database_domain("playlist_database");
 
 void
-playlist_vector_save(FILE *fp, const PlaylistVector &pv)
+playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv)
 {
 	for (const PlaylistInfo &pi : pv)
-		fprintf(fp, PLAYLIST_META_BEGIN "%s\n"
-			"mtime: %li\n"
-			"playlist_end\n",
-			pi.name.c_str(), (long)pi.mtime);
+		os.Format(PLAYLIST_META_BEGIN "%s\n"
+			  "mtime: %li\n"
+			  "playlist_end\n",
+			  pi.name.c_str(), (long)pi.mtime);
 }
 
 bool
diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx
index 48de64efa..17f82f64b 100644
--- a/src/PlaylistDatabase.hxx
+++ b/src/PlaylistDatabase.hxx
@@ -22,16 +22,15 @@
 
 #include "check.h"
 
-#include <stdio.h>
-
 #define PLAYLIST_META_BEGIN "playlist_begin: "
 
 class PlaylistVector;
+class BufferedOutputStream;
 class TextFile;
 class Error;
 
 void
-playlist_vector_save(FILE *fp, const PlaylistVector &pv);
+playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
 
 bool
 playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name,
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index d6c1dbdd7..02a7861e5 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -23,6 +23,7 @@
 #include "DetachedSong.hxx"
 #include "TagSave.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "tag/Tag.hxx"
 #include "tag/TagBuilder.hxx"
 #include "util/StringUtil.hxx"
@@ -38,38 +39,38 @@
 static constexpr Domain song_save_domain("song_save");
 
 static void
-range_save(FILE *file, unsigned start_ms, unsigned end_ms)
+range_save(BufferedOutputStream &os, unsigned start_ms, unsigned end_ms)
 {
 	if (end_ms > 0)
-		fprintf(file, "Range: %u-%u\n", start_ms, end_ms);
+		os.Format("Range: %u-%u\n", start_ms, end_ms);
 	else if (start_ms > 0)
-		fprintf(file, "Range: %u-\n", start_ms);
+		os.Format("Range: %u-\n", start_ms);
 }
 
 void
-song_save(FILE *fp, const Song &song)
+song_save(BufferedOutputStream &os, const Song &song)
 {
-	fprintf(fp, SONG_BEGIN "%s\n", song.uri);
+	os.Format(SONG_BEGIN "%s\n", song.uri);
 
-	range_save(fp, song.start_ms, song.end_ms);
+	range_save(os, song.start_ms, song.end_ms);
 
-	tag_save(fp, song.tag);
+	tag_save(os, song.tag);
 
-	fprintf(fp, SONG_MTIME ": %li\n", (long)song.mtime);
-	fprintf(fp, SONG_END "\n");
+	os.Format(SONG_MTIME ": %li\n", (long)song.mtime);
+	os.Format(SONG_END "\n");
 }
 
 void
-song_save(FILE *fp, const DetachedSong &song)
+song_save(BufferedOutputStream &os, const DetachedSong &song)
 {
-	fprintf(fp, SONG_BEGIN "%s\n", song.GetURI());
+	os.Format(SONG_BEGIN "%s\n", song.GetURI());
 
-	range_save(fp, song.GetStartMS(), song.GetEndMS());
+	range_save(os, song.GetStartMS(), song.GetEndMS());
 
-	tag_save(fp, song.GetTag());
+	tag_save(os, song.GetTag());
 
-	fprintf(fp, SONG_MTIME ": %li\n", (long)song.GetLastModified());
-	fprintf(fp, SONG_END "\n");
+	os.Format(SONG_MTIME ": %li\n", (long)song.GetLastModified());
+	os.Format(SONG_END "\n");
 }
 
 DetachedSong *
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
index 2a0edb49d..28c217249 100644
--- a/src/SongSave.hxx
+++ b/src/SongSave.hxx
@@ -20,21 +20,20 @@
 #ifndef MPD_SONG_SAVE_HXX
 #define MPD_SONG_SAVE_HXX
 
-#include <stdio.h>
-
 #define SONG_BEGIN "song_begin: "
 
 struct Song;
 struct Directory;
 class DetachedSong;
+class BufferedOutputStream;
 class TextFile;
 class Error;
 
 void
-song_save(FILE *fp, const Song &song);
+song_save(BufferedOutputStream &os, const Song &song);
 
 void
-song_save(FILE *fp, const DetachedSong &song);
+song_save(BufferedOutputStream &os, const DetachedSong &song);
 
 /**
  * Loads a song from the input file.  Reading stops after the
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index 0e558f298..e8df1ec0a 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -22,6 +22,8 @@
 #include "output/OutputState.hxx"
 #include "queue/PlaylistState.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/FileOutputStream.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "Partition.hxx"
 #include "Instance.hxx"
 #include "mixer/Volume.hxx"
@@ -62,25 +64,35 @@ StateFile::IsModified() const
 								 partition.pc);
 }
 
+inline void
+StateFile::Write(BufferedOutputStream &os)
+{
+	save_sw_volume_state(os);
+	audio_output_state_save(os, partition.outputs);
+	playlist_state_save(os, partition.playlist, partition.pc);
+}
+
+inline bool
+StateFile::Write(OutputStream &os, Error &error)
+{
+	BufferedOutputStream bos(os);
+	Write(bos);
+	return bos.Flush(error);
+}
+
 void
 StateFile::Write()
 {
 	FormatDebug(state_file_domain,
 		    "Saving state file %s", path_utf8.c_str());
 
-	FILE *fp = FOpen(path, FOpenMode::WriteText);
-	if (gcc_unlikely(!fp)) {
-		FormatErrno(state_file_domain, "failed to create %s",
-			    path_utf8.c_str());
+	Error error;
+	FileOutputStream fos(path, error);
+	if (!fos.IsDefined() || !Write(fos, error) || !fos.Commit(error)) {
+		LogError(error);
 		return;
 	}
 
-	save_sw_volume_state(fp);
-	audio_output_state_save(fp, partition.outputs);
-	playlist_state_save(fp, partition.playlist, partition.pc);
-
-	fclose(fp);
-
 	RememberVersions();
 }
 
diff --git a/src/StateFile.hxx b/src/StateFile.hxx
index e35797b95..609651c92 100644
--- a/src/StateFile.hxx
+++ b/src/StateFile.hxx
@@ -27,6 +27,8 @@
 #include <string>
 
 struct Partition;
+class OutputStream;
+class BufferedOutputStream;
 
 class StateFile final : private TimeoutMonitor {
 	AllocatedPath path;
@@ -53,6 +55,9 @@ public:
 	void CheckModified();
 
 private:
+	bool Write(OutputStream &os, Error &error);
+	void Write(BufferedOutputStream &os);
+
 	/**
 	 * Save the current state versions for use with IsModified().
 	 */
diff --git a/src/TagSave.cxx b/src/TagSave.cxx
index 7666287af..6ec562ec5 100644
--- a/src/TagSave.cxx
+++ b/src/TagSave.cxx
@@ -20,19 +20,19 @@
 #include "config.h"
 #include "TagSave.hxx"
 #include "tag/Tag.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 
 #define SONG_TIME "Time: "
 
 void
-tag_save(FILE *file, const Tag &tag)
+tag_save(BufferedOutputStream &os, const Tag &tag)
 {
 	if (tag.time >= 0)
-		fprintf(file, SONG_TIME "%i\n", tag.time);
+		os.Format(SONG_TIME "%i\n", tag.time);
 
 	if (tag.has_playlist)
-		fprintf(file, "Playlist: yes\n");
+		os.Format("Playlist: yes\n");
 
 	for (const auto &i : tag)
-		fprintf(file, "%s: %s\n",
-			tag_item_names[i.type], i.value);
+		os.Format("%s: %s\n", tag_item_names[i.type], i.value);
 }
diff --git a/src/TagSave.hxx b/src/TagSave.hxx
index d209c0a16..fd4b91f98 100644
--- a/src/TagSave.hxx
+++ b/src/TagSave.hxx
@@ -20,11 +20,10 @@
 #ifndef MPD_TAG_SAVE_HXX
 #define MPD_TAG_SAVE_HXX
 
-#include <stdio.h>
-
 struct Tag;
+class BufferedOutputStream;
 
 void
-tag_save(FILE *file, const Tag &tag);
+tag_save(BufferedOutputStream &os, const Tag &tag);
 
 #endif
diff --git a/src/db/plugins/simple/DatabaseSave.cxx b/src/db/plugins/simple/DatabaseSave.cxx
index 62034a0b8..e10d79d60 100644
--- a/src/db/plugins/simple/DatabaseSave.cxx
+++ b/src/db/plugins/simple/DatabaseSave.cxx
@@ -23,6 +23,7 @@
 #include "db/DatabaseError.hxx"
 #include "Directory.hxx"
 #include "DirectorySave.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "fs/TextFile.hxx"
 #include "tag/Tag.hxx"
 #include "tag/TagSettings.h"
@@ -49,20 +50,20 @@ static constexpr unsigned DB_FORMAT = 2;
 static constexpr unsigned OLDEST_DB_FORMAT = 1;
 
 void
-db_save_internal(FILE *fp, const Directory &music_root)
+db_save_internal(BufferedOutputStream &os, const Directory &music_root)
 {
-	fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
-	fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
-	fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
-	fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, GetFSCharset());
+	os.Format("%s\n", DIRECTORY_INFO_BEGIN);
+	os.Format(DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
+	os.Format("%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
+	os.Format("%s%s\n", DIRECTORY_FS_CHARSET, GetFSCharset());
 
 	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
 		if (!ignore_tag_items[i])
-			fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
+			os.Format(DB_TAG_PREFIX "%s\n", tag_item_names[i]);
 
-	fprintf(fp, "%s\n", DIRECTORY_INFO_END);
+	os.Format("%s\n", DIRECTORY_INFO_END);
 
-	directory_save(fp, music_root);
+	directory_save(os, music_root);
 }
 
 bool
diff --git a/src/db/plugins/simple/DatabaseSave.hxx b/src/db/plugins/simple/DatabaseSave.hxx
index 3bd3377ae..bb7f57115 100644
--- a/src/db/plugins/simple/DatabaseSave.hxx
+++ b/src/db/plugins/simple/DatabaseSave.hxx
@@ -20,14 +20,13 @@
 #ifndef MPD_DATABASE_SAVE_HXX
 #define MPD_DATABASE_SAVE_HXX
 
-#include <stdio.h>
-
 struct Directory;
+class BufferedOutputStream;
 class TextFile;
 class Error;
 
 void
-db_save_internal(FILE *file, const Directory &root);
+db_save_internal(BufferedOutputStream &os, const Directory &root);
 
 bool
 db_load_internal(TextFile &file, Directory &root, Error &error);
diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
index 9d3ebbac2..7e7cbc3ba 100644
--- a/src/db/plugins/simple/DirectorySave.cxx
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -25,6 +25,7 @@
 #include "DetachedSong.hxx"
 #include "PlaylistDatabase.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "util/StringUtil.hxx"
 #include "util/NumberParser.hxx"
 #include "util/Error.hxx"
@@ -70,37 +71,37 @@ ParseTypeString(const char *type)
 }
 
 void
-directory_save(FILE *fp, const Directory &directory)
+directory_save(BufferedOutputStream &os, const Directory &directory)
 {
 	if (!directory.IsRoot()) {
 		const char *type = DeviceToTypeString(directory.device);
 		if (type != nullptr)
-			fprintf(fp, DIRECTORY_TYPE "%s\n", type);
+			os.Format(DIRECTORY_TYPE "%s\n", type);
 
 		if (directory.mtime != 0)
-			fprintf(fp, DIRECTORY_MTIME "%lu\n",
-				(unsigned long)directory.mtime);
+			os.Format(DIRECTORY_MTIME "%lu\n",
+				  (unsigned long)directory.mtime);
 
-		fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, directory.GetPath());
+		os.Format("%s%s\n", DIRECTORY_BEGIN, directory.GetPath());
 	}
 
 	for (const auto &child : directory.children) {
-		fprintf(fp, DIRECTORY_DIR "%s\n", child.GetName());
+		os.Format(DIRECTORY_DIR "%s\n", child.GetName());
 
 		if (!child.IsMount())
-			directory_save(fp, child);
+			directory_save(os, child);
 
-		if (ferror(fp))
+		if (!os.Check())
 			return;
 	}
 
 	for (const auto &song : directory.songs)
-		song_save(fp, song);
+		song_save(os, song);
 
-	playlist_vector_save(fp, directory.playlists);
+	playlist_vector_save(os, directory.playlists);
 
 	if (!directory.IsRoot())
-		fprintf(fp, DIRECTORY_END "%s\n", directory.GetPath());
+		os.Format(DIRECTORY_END "%s\n", directory.GetPath());
 }
 
 static bool
diff --git a/src/db/plugins/simple/DirectorySave.hxx b/src/db/plugins/simple/DirectorySave.hxx
index 07e9e158b..f464f9946 100644
--- a/src/db/plugins/simple/DirectorySave.hxx
+++ b/src/db/plugins/simple/DirectorySave.hxx
@@ -20,14 +20,13 @@
 #ifndef MPD_DIRECTORY_SAVE_HXX
 #define MPD_DIRECTORY_SAVE_HXX
 
-#include <stdio.h>
-
 struct Directory;
 class TextFile;
+class BufferedOutputStream;
 class Error;
 
 void
-directory_save(FILE *fp, const Directory &directory);
+directory_save(BufferedOutputStream &os, const Directory &directory);
 
 bool
 directory_load(TextFile &file, Directory &directory, Error &error);
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
index 97d3e52df..fe712b90b 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -32,6 +32,8 @@
 #include "db/DatabaseLock.hxx"
 #include "db/DatabaseError.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
+#include "fs/output/FileOutputStream.hxx"
 #include "config/ConfigData.hxx"
 #include "fs/FileSystem.hxx"
 #include "util/CharUtil.hxx"
@@ -366,22 +368,16 @@ SimpleDatabase::Save(Error &error)
 
 	LogDebug(simple_db_domain, "writing DB");
 
-	FILE *fp = FOpen(path, FOpenMode::WriteText);
-	if (!fp) {
-		error.FormatErrno("unable to write to db file \"%s\"",
-				  path_utf8.c_str());
+	FileOutputStream fos(path, error);
+	if (!fos.IsDefined())
 		return false;
-	}
 
-	db_save_internal(fp, *root);
+	BufferedOutputStream bos(fos);
 
-	if (ferror(fp)) {
-		error.SetErrno("Failed to write to database file");
-		fclose(fp);
+	db_save_internal(bos, *root);
+
+	if (!bos.Flush(error) || !fos.Commit(error))
 		return false;
-	}
-
-	fclose(fp);
 
 	struct stat st;
 	if (StatFile(path, st))
diff --git a/src/mixer/Volume.cxx b/src/mixer/Volume.cxx
index 596b3c12a..86ebbf422 100644
--- a/src/mixer/Volume.cxx
+++ b/src/mixer/Volume.cxx
@@ -24,6 +24,7 @@
 #include "util/StringUtil.hxx"
 #include "util/Domain.hxx"
 #include "system/PeriodClock.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "Log.hxx"
 
 #include <assert.h>
@@ -110,9 +111,10 @@ read_sw_volume_state(const char *line, MultipleOutputs &outputs)
 	return true;
 }
 
-void save_sw_volume_state(FILE *fp)
+void
+save_sw_volume_state(BufferedOutputStream &os)
 {
-	fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set);
+	os.Format(SW_VOLUME_STATE "%u\n", volume_software_set);
 }
 
 unsigned
diff --git a/src/mixer/Volume.hxx b/src/mixer/Volume.hxx
index a02c21a21..d787a6415 100644
--- a/src/mixer/Volume.hxx
+++ b/src/mixer/Volume.hxx
@@ -22,9 +22,8 @@
 
 #include "Compiler.h"
 
-#include <stdio.h>
-
 class MultipleOutputs;
+class BufferedOutputStream;
 
 void
 InvalidateHardwareVolume();
@@ -39,7 +38,8 @@ volume_level_change(MultipleOutputs &outputs, unsigned volume);
 bool
 read_sw_volume_state(const char *line, MultipleOutputs &outputs);
 
-void save_sw_volume_state(FILE *fp);
+void
+save_sw_volume_state(BufferedOutputStream &os);
 
 /**
  * Generates a hash number for the current state of the software
diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx
index 7f3a8da7b..0c1424404 100644
--- a/src/output/OutputState.cxx
+++ b/src/output/OutputState.cxx
@@ -28,6 +28,7 @@
 #include "Internal.hxx"
 #include "Domain.hxx"
 #include "Log.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "util/StringUtil.hxx"
 
 #include <assert.h>
@@ -38,13 +39,13 @@
 unsigned audio_output_state_version;
 
 void
-audio_output_state_save(FILE *fp, const MultipleOutputs &outputs)
+audio_output_state_save(BufferedOutputStream &os,
+			const MultipleOutputs &outputs)
 {
 	for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
 		const AudioOutput &ao = outputs.Get(i);
 
-		fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
-			ao.enabled, ao.name);
+		os.Format(AUDIO_DEVICE_STATE "%d:%s\n", ao.enabled, ao.name);
 	}
 }
 
diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx
index 32f4bbcce..47f8429d5 100644
--- a/src/output/OutputState.hxx
+++ b/src/output/OutputState.hxx
@@ -25,15 +25,15 @@
 #ifndef MPD_OUTPUT_STATE_HXX
 #define MPD_OUTPUT_STATE_HXX
 
-#include <stdio.h>
-
 class MultipleOutputs;
+class BufferedOutputStream;
 
 bool
 audio_output_state_read(const char *line, MultipleOutputs &outputs);
 
 void
-audio_output_state_save(FILE *fp, const MultipleOutputs &outputs);
+audio_output_state_save(BufferedOutputStream &os,
+			const MultipleOutputs &outputs);
 
 /**
  * Generates a version number for the current state of the audio
diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx
index f5c798e3e..56adb2a9b 100644
--- a/src/queue/PlaylistState.cxx
+++ b/src/queue/PlaylistState.cxx
@@ -28,6 +28,7 @@
 #include "Playlist.hxx"
 #include "queue/QueueSave.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "PlayerControl.hxx"
 #include "config/ConfigGlobal.hxx"
 #include "config/ConfigOption.hxx"
@@ -59,47 +60,45 @@
 #define PLAYLIST_BUFFER_SIZE	2*MPD_PATH_MAX
 
 void
-playlist_state_save(FILE *fp, const struct playlist &playlist,
+playlist_state_save(BufferedOutputStream &os, const struct playlist &playlist,
 		    PlayerControl &pc)
 {
 	const auto player_status = pc.GetStatus();
 
-	fputs(PLAYLIST_STATE_FILE_STATE, fp);
+	os.Write(PLAYLIST_STATE_FILE_STATE);
 
 	if (playlist.playing) {
 		switch (player_status.state) {
 		case PlayerState::PAUSE:
-			fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp);
+			os.Write(PLAYLIST_STATE_FILE_STATE_PAUSE "\n");
 			break;
 		default:
-			fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp);
+			os.Write(PLAYLIST_STATE_FILE_STATE_PLAY "\n");
 		}
-		fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
-			playlist.queue.OrderToPosition(playlist.current));
-		fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n",
-			(int)player_status.elapsed_time);
+		os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n",
+			  playlist.queue.OrderToPosition(playlist.current));
+		os.Format(PLAYLIST_STATE_FILE_TIME "%i\n",
+			  (int)player_status.elapsed_time);
 	} else {
-		fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp);
+		os.Write(PLAYLIST_STATE_FILE_STATE_STOP "\n");
 
 		if (playlist.current >= 0)
-			fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n",
+			os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n",
 				playlist.queue.OrderToPosition(playlist.current));
 	}
 
-	fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random);
-	fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat);
-	fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single);
-	fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n",
-		playlist.queue.consume);
-	fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
-		(int)pc.GetCrossFade());
-	fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n",
-		pc.GetMixRampDb());
-	fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
-		pc.GetMixRampDelay());
-	fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp);
-	queue_save(fp, playlist.queue);
-	fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp);
+	os.Format(PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random);
+	os.Format(PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat);
+	os.Format(PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single);
+	os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume);
+	os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
+		  (int)pc.GetCrossFade());
+	os.Format(PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc.GetMixRampDb());
+	os.Format(PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
+		  pc.GetMixRampDelay());
+	os.Write(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n");
+	queue_save(os, playlist.queue);
+	os.Write(PLAYLIST_STATE_FILE_PLAYLIST_END "\n");
 }
 
 static void
diff --git a/src/queue/PlaylistState.hxx b/src/queue/PlaylistState.hxx
index 8d3f88ae2..3211b1178 100644
--- a/src/queue/PlaylistState.hxx
+++ b/src/queue/PlaylistState.hxx
@@ -25,15 +25,14 @@
 #ifndef MPD_PLAYLIST_STATE_HXX
 #define MPD_PLAYLIST_STATE_HXX
 
-#include <stdio.h>
-
 struct playlist;
 struct PlayerControl;
 class TextFile;
+class BufferedOutputStream;
 class SongLoader;
 
 void
-playlist_state_save(FILE *fp, const playlist &playlist,
+playlist_state_save(BufferedOutputStream &os, const playlist &playlist,
 		    PlayerControl &pc);
 
 bool
diff --git a/src/queue/QueueSave.cxx b/src/queue/QueueSave.cxx
index 5f47a0fac..eca906421 100644
--- a/src/queue/QueueSave.cxx
+++ b/src/queue/QueueSave.cxx
@@ -26,6 +26,7 @@
 #include "SongLoader.hxx"
 #include "playlist/PlaylistSong.hxx"
 #include "fs/TextFile.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
 #include "util/StringUtil.hxx"
 #include "util/Error.hxx"
 #include "fs/Traits.hxx"
@@ -36,40 +37,41 @@
 #define PRIO_LABEL "Prio: "
 
 static void
-queue_save_database_song(FILE *fp, int idx, const DetachedSong &song)
+queue_save_database_song(BufferedOutputStream &os,
+			 int idx, const DetachedSong &song)
 {
-	fprintf(fp, "%i:%s\n", idx, song.GetURI());
+	os.Format("%i:%s\n", idx, song.GetURI());
 }
 
 static void
-queue_save_full_song(FILE *fp, const DetachedSong &song)
+queue_save_full_song(BufferedOutputStream &os, const DetachedSong &song)
 {
-	song_save(fp, song);
+	song_save(os, song);
 }
 
 static void
-queue_save_song(FILE *fp, int idx, const DetachedSong &song)
+queue_save_song(BufferedOutputStream &os, int idx, const DetachedSong &song)
 {
 	if (song.IsInDatabase() &&
 	    song.GetStartMS() == 0 && song.GetEndMS() == 0)
 		/* use the brief format (just the URI) for "full"
 		   database songs */
-		queue_save_database_song(fp, idx, song);
+		queue_save_database_song(os, idx, song);
 	else
 		/* use the long format (URI, range, tags) for the
 		   rest, so all metadata survives a MPD restart */
-		queue_save_full_song(fp, song);
+		queue_save_full_song(os, song);
 }
 
 void
-queue_save(FILE *fp, const Queue &queue)
+queue_save(BufferedOutputStream &os, const Queue &queue)
 {
 	for (unsigned i = 0; i < queue.GetLength(); i++) {
 		uint8_t prio = queue.GetPriorityAtPosition(i);
 		if (prio != 0)
-			fprintf(fp, PRIO_LABEL "%u\n", prio);
+			os.Format(PRIO_LABEL "%u\n", prio);
 
-		queue_save_song(fp, i, queue.Get(i));
+		queue_save_song(os, i, queue.Get(i));
 	}
 }
 
diff --git a/src/queue/QueueSave.hxx b/src/queue/QueueSave.hxx
index 470823a24..3fb4dc1a6 100644
--- a/src/queue/QueueSave.hxx
+++ b/src/queue/QueueSave.hxx
@@ -25,14 +25,13 @@
 #ifndef MPD_QUEUE_SAVE_HXX
 #define MPD_QUEUE_SAVE_HXX
 
-#include <stdio.h>
-
 struct Queue;
+class BufferedOutputStream;
 class TextFile;
 class SongLoader;
 
 void
-queue_save(FILE *fp, const Queue &queue);
+queue_save(BufferedOutputStream &os, const Queue &queue);
 
 /**
  * Loads one song from the state file and appends it to the queue.
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index 5928c8eb4..1d497535c 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -29,6 +29,8 @@
 #include "playlist/PlaylistRegistry.hxx"
 #include "playlist/PlaylistPlugin.hxx"
 #include "fs/Path.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
+#include "fs/output/StdioOutputStream.hxx"
 #include "util/Error.hxx"
 #include "thread/Cond.hxx"
 #include "Log.hxx"
@@ -40,6 +42,15 @@
 #include <unistd.h>
 #include <stdlib.h>
 
+static void
+tag_save(FILE *file, const Tag &tag)
+{
+	StdioOutputStream sos(file);
+	BufferedOutputStream bos(sos);
+	tag_save(bos, tag);
+	bos.Flush();
+}
+
 int main(int argc, char **argv)
 {
 	const char *uri;
diff --git a/test/run_input.cxx b/test/run_input.cxx
index 130d0f031..f53711cc9 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -28,6 +28,8 @@
 #include "util/Error.hxx"
 #include "thread/Cond.hxx"
 #include "Log.hxx"
+#include "fs/output/BufferedOutputStream.hxx"
+#include "fs/output/StdioOutputStream.hxx"
 
 #ifdef ENABLE_ARCHIVE
 #include "archive/ArchiveList.hxx"
@@ -40,6 +42,15 @@
 #include <unistd.h>
 #include <stdlib.h>
 
+static void
+tag_save(FILE *file, const Tag &tag)
+{
+	StdioOutputStream sos(file);
+	BufferedOutputStream bos(sos);
+	tag_save(bos, tag);
+	bos.Flush();
+}
+
 static int
 dump_input_stream(InputStream *is)
 {