1// Copyright 2011 The Kyua Authors.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9//   notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright
11//   notice, this list of conditions and the following disclaimer in the
12//   documentation and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors
14//   may be used to endorse or promote products derived from this software
15//   without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29#include "cli/config.hpp"
30
31#include "cli/common.hpp"
32#include "engine/config.hpp"
33#include "engine/exceptions.hpp"
34#include "utils/cmdline/options.hpp"
35#include "utils/cmdline/parser.ipp"
36#include "utils/config/tree.ipp"
37#include "utils/format/macros.hpp"
38#include "utils/fs/exceptions.hpp"
39#include "utils/fs/operations.hpp"
40#include "utils/fs/path.hpp"
41#include "utils/env.hpp"
42#include "utils/logging/macros.hpp"
43#include "utils/optional.ipp"
44
45namespace cmdline = utils::cmdline;
46namespace config = utils::config;
47namespace fs = utils::fs;
48
49using utils::optional;
50
51
52namespace {
53
54
55/// Basename of the configuration file.
56static const char* config_basename = "kyua.conf";
57
58
59/// Magic string to disable loading of configuration files.
60static const char* none_config = "none";
61
62
63/// Textual description of the default configuration files.
64///
65/// This is just an auxiliary string required to define the option below, which
66/// requires a pointer to a static C string.
67///
68/// \todo If the user overrides the KYUA_CONFDIR environment variable, we don't
69/// reflect this fact here.  We don't want to query the variable during program
70/// initialization due to the side-effects it may have.  Therefore, fixing this
71/// is tricky as it may require a whole rethink of this module.
72static const std::string config_lookup_names =
73    (fs::path("~/.kyua") / config_basename).str() + " or " +
74    (fs::path(KYUA_CONFDIR) / config_basename).str();
75
76
77/// Loads the configuration file for this session, if any.
78///
79/// This is a helper function that does not apply user-specified overrides.  See
80/// the documentation for cli::load_config() for more details.
81///
82/// \param cmdline The parsed command line.
83///
84/// \return The loaded configuration file, or the configuration defaults if the
85/// loading is disabled.
86///
87/// \throw engine::error If the parsing of the configuration file fails.
88///     TODO(jmmv): I'm not sure if this is the raised exception.  And even if
89///     it is, we should make it more accurate.
90config::tree
91load_config_file(const cmdline::parsed_cmdline& cmdline)
92{
93    // TODO(jmmv): We should really be able to use cmdline.has_option here to
94    // detect whether the option was provided or not instead of checking against
95    // the default value.
96    const fs::path filename = cmdline.get_option< cmdline::path_option >(
97        cli::config_option.long_name());
98    if (filename.str() == none_config) {
99        LD("Configuration loading disabled; using defaults");
100        return engine::default_config();
101    } else if (filename.str() != cli::config_option.default_value())
102        return engine::load_config(filename);
103
104    const optional< fs::path > home = utils::get_home();
105    if (home) {
106        const fs::path path = home.get() / ".kyua" / config_basename;
107        try {
108            if (fs::exists(path))
109                return engine::load_config(path);
110        } catch (const fs::error& e) {
111            // Fall through.  If we fail to load the user-specific configuration
112            // file because it cannot be openend, we try to load the system-wide
113            // one.
114            LW(F("Failed to load user-specific configuration file '%s': %s") %
115               path % e.what());
116        }
117    }
118
119    const fs::path confdir(utils::getenv_with_default(
120        "KYUA_CONFDIR", KYUA_CONFDIR));
121
122    const fs::path path = confdir / config_basename;
123    if (fs::exists(path)) {
124        return engine::load_config(path);
125    } else {
126        return engine::default_config();
127    }
128}
129
130
131/// Loads the configuration file for this session, if any.
132///
133/// This is a helper function for cli::load_config() that attempts to load the
134/// configuration unconditionally.
135///
136/// \param cmdline The parsed command line.
137///
138/// \return The loaded configuration file data.
139///
140/// \throw engine::error If the parsing of the configuration file fails.
141static config::tree
142load_required_config(const cmdline::parsed_cmdline& cmdline)
143{
144    config::tree user_config = load_config_file(cmdline);
145
146    if (cmdline.has_option(cli::variable_option.long_name())) {
147        typedef std::pair< std::string, std::string > override_pair;
148
149        const std::vector< override_pair >& overrides =
150            cmdline.get_multi_option< cmdline::property_option >(
151                cli::variable_option.long_name());
152
153        for (std::vector< override_pair >::const_iterator
154                 iter = overrides.begin(); iter != overrides.end(); iter++) {
155            try {
156                user_config.set_string((*iter).first, (*iter).second);
157            } catch (const config::error& e) {
158                // TODO(jmmv): Raising this type from here is obviously the
159                // wrong thing to do.
160                throw engine::error(e.what());
161            }
162        }
163    }
164
165    return user_config;
166}
167
168
169}  // anonymous namespace
170
171
172/// Standard definition of the option to specify a configuration file.
173///
174/// You must use load_config() to load a configuration file while honoring the
175/// value of this flag.
176const cmdline::path_option cli::config_option(
177    'c', "config",
178    (std::string("Path to the configuration file; '") + none_config +
179     "' to disable loading").c_str(),
180    "file", config_lookup_names.c_str());
181
182
183/// Standard definition of the option to specify a configuration variable.
184const cmdline::property_option cli::variable_option(
185    'v', "variable",
186    "Overrides a particular configuration variable",
187    "K=V");
188
189
190/// Loads the configuration file for this session, if any.
191///
192/// The algorithm implemented here is as follows:
193/// 1) If ~/.kyua/kyua.conf exists, load it.
194/// 2) Otherwise, if sysconfdir/kyua.conf exists, load it.
195/// 3) Otherwise, use the built-in settings.
196/// 4) Lastly, apply any user-provided overrides.
197///
198/// \param cmdline The parsed command line.
199/// \param required Whether the loading of the configuration file must succeed.
200///     Some commands should run regardless, and therefore we need to set this
201///     to false for those commands.
202///
203/// \return The loaded configuration file data.  If required was set to false,
204/// this might be the default configuration data if the requested file could not
205/// be properly loaded.
206///
207/// \throw engine::error If the parsing of the configuration file fails.
208config::tree
209cli::load_config(const cmdline::parsed_cmdline& cmdline,
210                 const bool required)
211{
212    try {
213        return load_required_config(cmdline);
214    } catch (const engine::error& e) {
215        if (required) {
216            throw;
217        } else {
218            LW(F("Ignoring failure to load configuration because the requested "
219                 "command should not fail: %s") % e.what());
220            return engine::default_config();
221        }
222    }
223}
224