diff --git a/Makefile.am b/Makefile.am index e836c7343..ebe358d6d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -389,6 +389,7 @@ endif # Generic utility library libutil_a_SOURCES = \ + src/util/RuntimeError.hxx \ src/util/Macros.hxx \ src/util/Cast.hxx \ src/util/Clamp.hxx \ diff --git a/src/Main.cxx b/src/Main.cxx index e76806256..de950c05a 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -444,11 +444,11 @@ int mpd_main(int argc, char *argv[]) io_thread_init(); config_global_init(); + try { #ifdef ANDROID - (void)argc; - (void)argv; + (void)argc; + (void)argv; - { const auto sdcard = Environment::getExternalStorageDirectory(); if (!sdcard.IsNull()) { const auto config_path = @@ -459,13 +459,16 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } } - } #else - if (!parse_cmdline(argc, argv, &options, error)) { - LogError(error); + if (!parse_cmdline(argc, argv, &options, error)) { + LogError(error); + return EXIT_FAILURE; + } +#endif + } catch (const std::exception &e) { + LogError(e); return EXIT_FAILURE; } -#endif #ifdef ENABLE_DAEMON if (!glue_daemonize_init(&options, error)) { diff --git a/src/config/ConfigFile.cxx b/src/config/ConfigFile.cxx index a9bf694c3..4a33efee8 100644 --- a/src/config/ConfigFile.cxx +++ b/src/config/ConfigFile.cxx @@ -27,6 +27,7 @@ #include "util/StringUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "util/RuntimeError.hxx" #include "fs/Path.hxx" #include "fs/io/FileReader.hxx" #include "fs/io/BufferedReader.hxx" @@ -54,27 +55,20 @@ config_read_name_value(ConfigBlock &block, char *input, unsigned line, const char *value = tokenizer.NextString(error); if (value == nullptr) { - if (tokenizer.IsEnd()) { - error.Set(config_file_domain, "Value missing"); - } else { - assert(error.IsDefined()); - } + if (tokenizer.IsEnd()) + throw std::runtime_error("Value missing"); + assert(error.IsDefined()); return false; } - if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) { - error.Set(config_file_domain, "Unknown tokens after value"); - return false; - } + if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) + throw std::runtime_error("Unknown tokens after value"); const BlockParam *bp = block.GetBlockParam(name); - if (bp != nullptr) { - error.Format(config_file_domain, - "\"%s\" is duplicate, first defined on line %i", - name, bp->line); - return false; - } + if (bp != nullptr) + throw FormatRuntimeError("\"%s\" is duplicate, first defined on line %i", + name, bp->line); block.AddBlockParam(name, value, line); return true; @@ -82,15 +76,14 @@ config_read_name_value(ConfigBlock &block, char *input, unsigned line, static ConfigBlock * config_read_block(BufferedReader &reader, Error &error) -{ +try { std::unique_ptr block(new ConfigBlock(reader.GetLineNumber())); while (true) { char *line = reader.ReadLine(); if (line == nullptr) { if (reader.Check(error)) - error.Set(config_file_domain, - "Expected '}' before end-of-file"); + throw std::runtime_error("Expected '}' before end-of-file"); return nullptr; } @@ -103,12 +96,8 @@ config_read_block(BufferedReader &reader, Error &error) (and from this "while" loop) */ line = StripLeft(line + 1); - if (*line != 0 && *line != CONF_COMMENT) { - error.Format(config_file_domain, - "line %u: Unknown tokens after '}'", - reader.GetLineNumber()); - return nullptr; - } + if (*line != 0 && *line != CONF_COMMENT) + throw std::runtime_error("Unknown tokens after '}'"); return block.release(); } @@ -123,6 +112,8 @@ config_read_block(BufferedReader &reader, Error &error) return nullptr; } } +} catch (...) { + std::throw_with_nested(FormatRuntimeError("Error in line %u", reader.GetLineNumber())); } gcc_nonnull_all @@ -150,30 +141,22 @@ ReadConfigBlock(ConfigData &config_data, BufferedReader &reader, if (head != nullptr && !option.repeatable) { ConfigBlock *block = head; - error.Format(config_file_domain, - "config parameter \"%s\" is first defined " - "on line %d and redefined on line %u\n", - name, block->line, - reader.GetLineNumber()); - return false; + throw FormatRuntimeError("config parameter \"%s\" is first defined " + "on line %d and redefined on line %u\n", + name, block->line, + reader.GetLineNumber()); } /* now parse the block or the value */ - if (tokenizer.CurrentChar() != '{') { - error.Format(config_file_domain, - "line %u: '{' expected", - reader.GetLineNumber()); - return false; - } + if (tokenizer.CurrentChar() != '{') + throw FormatRuntimeError("line %u: '{' expected", + reader.GetLineNumber()); char *line = StripLeft(tokenizer.Rest() + 1); - if (*line != 0 && *line != CONF_COMMENT) { - error.Format(config_file_domain, - "line %u: Unknown tokens after '{'", - reader.GetLineNumber()); - return false; - } + if (*line != 0 && *line != CONF_COMMENT) + throw FormatRuntimeError("line %u: Unknown tokens after '{'", + reader.GetLineNumber()); auto *param = config_read_block(reader, error); if (param == nullptr) @@ -208,12 +191,10 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader, if (head != nullptr && !option.repeatable) { struct config_param *param = head; - error.Format(config_file_domain, - "config parameter \"%s\" is first defined " - "on line %d and redefined on line %u\n", - name, param->line, - reader.GetLineNumber()); - return false; + throw FormatRuntimeError("config parameter \"%s\" is first defined " + "on line %d and redefined on line %u\n", + name, param->line, + reader.GetLineNumber()); } /* now parse the block or the value */ @@ -221,23 +202,16 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader, const char *value = tokenizer.NextString(error); if (value == nullptr) { if (tokenizer.IsEnd()) - error.Format(config_file_domain, - "line %u: Value missing", - reader.GetLineNumber()); - else - error.FormatPrefix("line %u: ", - reader.GetLineNumber()); + throw FormatRuntimeError("line %u: Value missing", + reader.GetLineNumber()); + error.FormatPrefix("line %u: ", reader.GetLineNumber()); return false; } - if (!tokenizer.IsEnd() && - tokenizer.CurrentChar() != CONF_COMMENT) { - error.Format(config_file_domain, - "line %u: Unknown tokens after value", - reader.GetLineNumber()); - return false; - } + if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) + throw FormatRuntimeError("line %u: Unknown tokens after value", + reader.GetLineNumber()); auto *param = new config_param(value, reader.GetLineNumber()); Append(head, param); @@ -281,11 +255,9 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Error &error) tokenizer, error)) return false; } else { - error.Format(config_file_domain, - "unrecognized parameter in config file at " - "line %u: %s\n", - reader.GetLineNumber(), name); - return false; + throw FormatRuntimeError("unrecognized parameter in config file at " + "line %u: %s\n", + reader.GetLineNumber(), name); } } } diff --git a/src/util/RuntimeError.hxx b/src/util/RuntimeError.hxx new file mode 100644 index 000000000..a25b71420 --- /dev/null +++ b/src/util/RuntimeError.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013-2015 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RUNTIME_ERROR_HXX +#define RUNTIME_ERROR_HXX + +#include +#include + +template +static inline std::runtime_error +FormatRuntimeError(const char *fmt, Args&&... args) noexcept +{ + char buffer[1024]; + snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); + return std::runtime_error(buffer); +} + +#endif diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx index 4f00006c7..034428d88 100644 --- a/test/DumpDatabase.cxx +++ b/test/DumpDatabase.cxx @@ -32,6 +32,7 @@ #include "tag/TagConfig.hxx" #include "fs/Path.hxx" #include "event/Loop.hxx" +#include "Log.hxx" #include "util/Error.hxx" #include @@ -89,7 +90,7 @@ DumpPlaylist(const PlaylistInfo &playlist, int main(int argc, char **argv) -{ +try { if (argc != 3) { cerr << "Usage: DumpDatabase CONFIG PLUGIN" << endl; return 1; @@ -158,4 +159,7 @@ main(int argc, char **argv) config_global_finish(); return EXIT_SUCCESS; -} + } catch (const std::exception &e) { + LogError(e); + return EXIT_FAILURE; + } diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx index 4a21e2df5..65e980b21 100644 --- a/test/dump_playlist.cxx +++ b/test/dump_playlist.cxx @@ -48,7 +48,7 @@ tag_save(FILE *file, const Tag &tag) } int main(int argc, char **argv) -{ +try { const char *uri; InputStream *is = NULL; @@ -144,5 +144,8 @@ int main(int argc, char **argv) input_stream_global_finish(); config_global_finish(); - return 0; -} + return EXIT_SUCCESS; + } catch (const std::exception &e) { + LogError(e); + return EXIT_FAILURE; + } diff --git a/test/read_conf.cxx b/test/read_conf.cxx index fdf93a40d..4d0fadcc6 100644 --- a/test/read_conf.cxx +++ b/test/read_conf.cxx @@ -28,7 +28,7 @@ #include int main(int argc, char **argv) -{ +try { if (argc != 3) { fprintf(stderr, "Usage: read_conf FILE SETTING\n"); return EXIT_FAILURE; @@ -60,4 +60,7 @@ int main(int argc, char **argv) config_global_finish(); return ret; -} + } catch (const std::exception &e) { + LogError(e); + return EXIT_FAILURE; + } diff --git a/test/run_filter.cxx b/test/run_filter.cxx index 6499b9631..912bc6470 100644 --- a/test/run_filter.cxx +++ b/test/run_filter.cxx @@ -67,7 +67,7 @@ load_filter(const char *name) } int main(int argc, char **argv) -{ +try { struct audio_format_string af_string; Error error2; char buffer[4096]; @@ -151,5 +151,8 @@ int main(int argc, char **argv) config_global_finish(); - return 0; -} + return EXIT_SUCCESS; + } catch (const std::exception &e) { + LogError(e); + return EXIT_FAILURE; + } diff --git a/test/run_neighbor_explorer.cxx b/test/run_neighbor_explorer.cxx index 29582fc55..3f48ee06f 100644 --- a/test/run_neighbor_explorer.cxx +++ b/test/run_neighbor_explorer.cxx @@ -46,7 +46,7 @@ class MyNeighborListener final : public NeighborListener { int main(int argc, char **argv) -{ +try { if (argc != 2) { fprintf(stderr, "Usage: run_neighbor_explorer CONFIG\n"); return EXIT_FAILURE; @@ -82,4 +82,7 @@ main(int argc, char **argv) loop.Run(); neighbor.Close(); return EXIT_SUCCESS; -} + } catch (const std::exception &e) { + LogError(e); + return EXIT_FAILURE; + } diff --git a/test/run_output.cxx b/test/run_output.cxx index 5100e8b44..f8f959a07 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -146,7 +146,7 @@ run_output(AudioOutput *ao, AudioFormat audio_format) } int main(int argc, char **argv) -{ +try { Error error; if (argc < 3 || argc > 4) { @@ -196,4 +196,7 @@ int main(int argc, char **argv) config_global_finish(); return success ? EXIT_SUCCESS : EXIT_FAILURE; -} + } catch (const std::exception &e) { + LogError(e); + return EXIT_FAILURE; + }