args.h raw
1 // Copyright (c) 2023 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #ifndef LIMENKA_COMMON_ARGS_H
6 #define LIMENKA_COMMON_ARGS_H
7
8 #include <common/settings.h>
9 #include <compat/compat.h>
10 #include <sync.h>
11 #include <util/chaintype.h>
12 #include <util/fs.h>
13
14 #include <iosfwd>
15 #include <list>
16 #include <map>
17 #include <optional>
18 #include <set>
19 #include <stdint.h>
20 #include <string>
21 #include <variant>
22 #include <vector>
23
24 class ArgsManager;
25
26 extern const char * const LIMENKA_CONF_FILENAME;
27 extern const char * const LIMENKA_SETTINGS_FILENAME;
28 extern const char * const LIMENKA_RW_CONF_FILENAME;
29
30 // Return true if -datadir option points to a valid directory or is not specified.
31 bool CheckDataDirOption(const ArgsManager& args);
32
33 /**
34 * Most paths passed as configuration arguments are treated as relative to
35 * the datadir if they are not absolute.
36 *
37 * @param args Parsed arguments and settings.
38 * @param path The path to be conditionally prefixed with datadir.
39 * @param net_specific Use network specific datadir variant
40 * @return The normalized path.
41 */
42 fs::path AbsPathForConfigVal(const ArgsManager& args, const fs::path& path, bool net_specific = true);
43
44 inline bool IsSwitchChar(char c)
45 {
46 #ifdef WIN32
47 return c == '-' || c == '/';
48 #else
49 return c == '-';
50 #endif
51 }
52
53 enum class OptionsCategory {
54 OPTIONS,
55 CONNECTION,
56 WALLET,
57 WALLET_DEBUG_TEST,
58 ZMQ,
59 DEBUG_TEST,
60 CHAINPARAMS,
61 NODE_RELAY,
62 BLOCK_CREATION,
63 RPC,
64 GUI,
65 COMMANDS,
66 REGISTER_COMMANDS,
67 CLI_COMMANDS,
68 IPC,
69 STATS,
70
71 HIDDEN // Always the last option to avoid printing these in the help
72 };
73
74 struct KeyInfo {
75 std::string name;
76 std::string section;
77 bool negated{false};
78 };
79
80 KeyInfo InterpretKey(std::string key);
81
82 std::optional<common::SettingsValue> InterpretValue(const KeyInfo& key, const std::string* value,
83 unsigned int flags, std::string& error);
84
85 struct SectionInfo {
86 std::string m_name;
87 std::string m_file;
88 int m_line;
89 };
90
91 std::string SettingToString(const common::SettingsValue&, const std::string&);
92 std::optional<std::string> SettingToString(const common::SettingsValue&);
93
94 int64_t SettingToInt(const common::SettingsValue&, int64_t);
95 std::optional<int64_t> SettingToInt(const common::SettingsValue&);
96
97 std::optional<int64_t> SettingToFixedPoint(const common::SettingsValue&, int decimals);
98
99 bool SettingToBool(const common::SettingsValue&, bool);
100 std::optional<bool> SettingToBool(const common::SettingsValue&);
101
102 void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change);
103
104 class ArgsManager
105 {
106 public:
107 /**
108 * Flags controlling how config and command line arguments are validated and
109 * interpreted.
110 */
111 enum Flags : uint32_t {
112 ALLOW_ANY = 0x01, //!< disable validation
113 // ALLOW_BOOL = 0x02, //!< unimplemented, draft implementation in #16545
114 // ALLOW_INT = 0x04, //!< unimplemented, draft implementation in #16545
115 // ALLOW_STRING = 0x08, //!< unimplemented, draft implementation in #16545
116 // ALLOW_LIST = 0x10, //!< unimplemented, draft implementation in #16545
117 DISALLOW_NEGATION = 0x20, //!< disallow -nofoo syntax
118 DISALLOW_ELISION = 0x40, //!< disallow -foo syntax that doesn't assign any value
119
120 DEBUG_ONLY = 0x100,
121 /* Some options would cause cross-contamination if values for
122 * mainnet were used while running on regtest/testnet (or vice-versa).
123 * Setting them as NETWORK_ONLY ensures that sharing a config file
124 * between mainnet and regtest/testnet won't cause problems due to these
125 * parameters by accident. */
126 NETWORK_ONLY = 0x200,
127 // This argument's value is sensitive (such as a password).
128 SENSITIVE = 0x400,
129 COMMAND = 0x800,
130 };
131
132 protected:
133 struct Arg
134 {
135 std::string m_help_param;
136 std::string m_help_text;
137 unsigned int m_flags;
138 };
139
140 mutable RecursiveMutex cs_args;
141 common::Settings m_settings GUARDED_BY(cs_args);
142 std::vector<std::string> m_command GUARDED_BY(cs_args);
143 std::string m_network GUARDED_BY(cs_args);
144 std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
145 std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
146 bool m_accept_any_command GUARDED_BY(cs_args){true};
147 std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
148 std::optional<fs::path> m_config_path GUARDED_BY(cs_args);
149 std::optional<fs::path> m_rwconf_path GUARDED_BY(cs_args);
150 bool m_rwconf_had_prune_option{false};
151 mutable fs::path m_cached_blocks_path GUARDED_BY(cs_args);
152 mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args);
153 mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args);
154
155 [[nodiscard]] bool ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys = false, std::map<std::string, std::vector<common::SettingsValue>>* settings_target = nullptr);
156
157 /**
158 * Returns true if settings values from the default section should be used,
159 * depending on the current network and whether the setting is
160 * network-specific.
161 */
162 bool UseDefaultSection(const std::string& arg) const EXCLUSIVE_LOCKS_REQUIRED(cs_args);
163
164 public:
165 /**
166 * Get setting value.
167 *
168 * Result will be null if setting was unset, true if "-setting" argument was passed
169 * false if "-nosetting" argument was passed, and a string if a "-setting=value"
170 * argument was passed.
171 */
172 common::SettingsValue GetSetting(const std::string& arg) const;
173
174 /**
175 * Get list of setting values.
176 */
177 std::vector<common::SettingsValue> GetSettingsList(const std::string& arg) const;
178
179 ArgsManager();
180 ~ArgsManager();
181
182 /**
183 * Select the network in use
184 */
185 void SelectConfigNetwork(const std::string& network);
186
187 [[nodiscard]] bool ParseParameters(int argc, const char* const argv[], std::string& error);
188
189 /**
190 * Return config file path (read-only)
191 */
192 fs::path GetConfigFilePath() const;
193 void SetConfigFilePath(fs::path);
194 fs::path GetRWConfigFilePath() const;
195 [[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false);
196
197 bool RWConfigHasPruneOption() const { return m_rwconf_had_prune_option; }
198 void ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, bool also_settings_json = true);
199 void ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, bool also_settings_json = true);
200 void EraseRWConfigFile();
201
202 /**
203 * Log warnings for options in m_section_only_args when
204 * they are specified in the default section but not overridden
205 * on the command line or in a network-specific section in the
206 * config file.
207 */
208 std::set<std::string> GetUnsuitableSectionOnlyArgs() const;
209
210 /**
211 * Log warnings for unrecognized section names in the config file.
212 */
213 std::list<SectionInfo> GetUnrecognizedSections() const;
214
215 struct Command {
216 /** The command (if one has been registered with AddCommand), or empty */
217 std::string command;
218 /**
219 * If command is non-empty: Any args that followed it
220 * If command is empty: The unregistered command and any args that followed it
221 */
222 std::vector<std::string> args;
223 };
224 /**
225 * Get the command and command args (returns std::nullopt if no command provided)
226 */
227 std::optional<const Command> GetCommand() const;
228
229 /**
230 * Get blocks directory path
231 *
232 * @return Blocks path which is network specific
233 */
234 fs::path GetBlocksDirPath() const;
235
236 /**
237 * Get data directory path
238 *
239 * @return Absolute path on success, otherwise an empty path when a non-directory path would be returned
240 */
241 fs::path GetDataDirBase() const { return GetDataDir(false); }
242
243 /**
244 * Get data directory path with appended network identifier
245 *
246 * @return Absolute path on success, otherwise an empty path when a non-directory path would be returned
247 */
248 fs::path GetDataDirNet() const { return GetDataDir(true); }
249
250 /**
251 * Clear cached directory paths
252 */
253 void ClearPathCache();
254
255 /**
256 * Return a vector of strings of the given argument
257 *
258 * @param strArg Argument to get (e.g. "-foo")
259 * @return command-line arguments
260 */
261 std::vector<std::string> GetArgs(const std::string& strArg) const;
262
263 /**
264 * Return true if the given argument has been manually set
265 *
266 * @param strArg Argument to get (e.g. "-foo")
267 * @return true if the argument has been set
268 */
269 bool IsArgSet(const std::string& strArg) const;
270
271 /**
272 * Return true if the argument was originally passed as a negated option,
273 * i.e. -nofoo.
274 *
275 * @param strArg Argument to get (e.g. "-foo")
276 * @return true if the argument was passed negated
277 */
278 bool IsArgNegated(const std::string& strArg) const;
279
280 /**
281 * Return string argument or default value
282 *
283 * @param strArg Argument to get (e.g. "-foo")
284 * @param strDefault (e.g. "1")
285 * @return command-line argument or default value
286 */
287 std::string GetArg(const std::string& strArg, const std::string& strDefault) const;
288 std::optional<std::string> GetArg(const std::string& strArg) const;
289
290 /**
291 * Return path argument or default value
292 *
293 * @param arg Argument to get a path from (e.g., "-datadir", "-blocksdir" or "-walletdir")
294 * @param default_value Optional default value to return instead of the empty path.
295 * @return normalized path if argument is set, with redundant "." and ".."
296 * path components and trailing separators removed (see patharg unit test
297 * for examples or implementation for details). If argument is empty or not
298 * set, default_value is returned unchanged.
299 */
300 fs::path GetPathArg(std::string arg, const fs::path& default_value = {}) const;
301
302 /**
303 * Return integer argument or default value
304 *
305 * @param strArg Argument to get (e.g. "-foo")
306 * @param nDefault (e.g. 1)
307 * @return command-line argument (0 if invalid number) or default value
308 */
309 int64_t GetIntArg(const std::string& strArg, int64_t nDefault) const;
310 std::optional<int64_t> GetIntArg(const std::string& strArg) const;
311
312 /**
313 * Return fixed-point argument
314 *
315 * @param arg Argument to get (e.g. "-foo")
316 * @param decimals Number of fractional decimal digits to accept
317 * @return Command-line argument (0 if invalid number) multiplied by 10**decimals
318 */
319 std::optional<int64_t> GetFixedPointArg(const std::string& arg, int decimals) const;
320
321 /**
322 * Return boolean argument or default value
323 *
324 * @param strArg Argument to get (e.g. "-foo")
325 * @param fDefault (true or false)
326 * @return command-line argument or default value
327 */
328 bool GetBoolArg(const std::string& strArg, bool fDefault) const;
329 std::optional<bool> GetBoolArg(const std::string& strArg) const;
330
331 /**
332 * Set an argument if it doesn't already have a value
333 *
334 * @param strArg Argument to set (e.g. "-foo")
335 * @param strValue Value (e.g. "1")
336 * @return true if argument gets set, false if it already had a value
337 */
338 bool SoftSetArg(const std::string& strArg, const std::string& strValue);
339
340 /**
341 * Set a boolean argument if it doesn't already have a value
342 *
343 * @param strArg Argument to set (e.g. "-foo")
344 * @param fValue Value (e.g. false)
345 * @return true if argument gets set, false if it already had a value
346 */
347 bool SoftSetBoolArg(const std::string& strArg, bool fValue);
348
349 // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already
350 // been set. Also called directly in testing.
351 void ForceSetArg(const std::string& arg, const std::string& value);
352 void ForceSetArg(const std::string& arg, int64_t value);
353 void ForceSetArgV(const std::string& arg, const common::SettingsValue& value);
354
355 /**
356 * Returns the appropriate chain type from the program arguments.
357 * @return ChainType::MAIN by default; raises runtime error if an invalid
358 * combination, or unknown chain is given.
359 */
360 ChainType GetChainType() const;
361
362 /**
363 * Returns the appropriate chain type string from the program arguments.
364 * @return ChainType::MAIN string by default; raises runtime error if an
365 * invalid combination is given.
366 */
367 std::string GetChainTypeString() const;
368
369 /**
370 * Add argument
371 */
372 void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat);
373
374 /**
375 * Add subcommand
376 */
377 void AddCommand(const std::string& cmd, const std::string& help);
378
379 /**
380 * Add many hidden arguments
381 */
382 void AddHiddenArgs(const std::vector<std::string>& args, unsigned int flags = ArgsManager::ALLOW_ANY);
383
384 /**
385 * Clear available arguments
386 */
387 void ClearArgs() {
388 LOCK(cs_args);
389 m_available_args.clear();
390 m_network_only_args.clear();
391 }
392
393 /**
394 * Check CLI command args
395 *
396 * @throws std::runtime_error when multiple CLI_COMMAND arguments are specified
397 */
398 void CheckMultipleCLIArgs() const;
399
400 /**
401 * Get the help string
402 */
403 std::string GetHelpMessage() const;
404
405 /**
406 * Return Flags for known arg.
407 * Return nullopt for unknown arg.
408 */
409 std::optional<unsigned int> GetArgFlags(const std::string& name) const;
410
411 /**
412 * Get settings file path, or return false if read-write settings were
413 * disabled with -nosettings.
414 */
415 bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false, bool backup = false) const;
416
417 /**
418 * Read settings file. Push errors to vector, or log them if null.
419 */
420 bool ReadSettingsFile(std::vector<std::string>* errors = nullptr);
421
422 /**
423 * Write settings file or backup settings file. Push errors to vector, or
424 * log them if null.
425 */
426 bool WriteSettingsFile(std::vector<std::string>* errors = nullptr, bool backup = false) const;
427
428 /**
429 * Get current setting from config file or read/write settings file,
430 * ignoring nonpersistent command line or forced settings values.
431 */
432 common::SettingsValue GetPersistentSetting(const std::string& name) const;
433
434 /**
435 * Access settings with lock held.
436 */
437 template <typename Fn>
438 void LockSettings(Fn&& fn)
439 {
440 LOCK(cs_args);
441 fn(m_settings);
442 }
443
444 /**
445 * Log the config file options and the command line arguments,
446 * useful for troubleshooting.
447 */
448 void LogArgs() const;
449
450 private:
451 /**
452 * Get data directory path
453 *
454 * @param net_specific Append network identifier to the returned path
455 * @return Absolute path on success, otherwise an empty path when a non-directory path would be returned
456 */
457 fs::path GetDataDir(bool net_specific) const;
458
459 /**
460 * Return -regtest/-signet/-testnet/-testnet4/-chain= setting as a ChainType enum if a
461 * recognized chain type was set, or as a string if an unrecognized chain
462 * name was set. Raise an exception if an invalid combination of flags was
463 * provided.
464 */
465 std::variant<ChainType, std::string> GetChainArg() const;
466
467 // Helper function for LogArgs().
468 void logArgsPrefix(
469 const std::string& prefix,
470 const std::string& section,
471 const std::map<std::string, std::vector<common::SettingsValue>>& args) const;
472 };
473
474 extern ArgsManager gArgs;
475
476 /**
477 * @return true if help has been requested via a command-line arg
478 */
479 bool HelpRequested(const ArgsManager& args);
480
481 /** Add help options to the args manager */
482 void SetupHelpOptions(ArgsManager& args);
483
484 extern const std::vector<std::string> TEST_OPTIONS_DOC;
485
486 /** Checks if a particular test option is present in -test command-line arg options */
487 bool HasTestOption(const ArgsManager& args, const std::string& test_option);
488
489 /**
490 * Format a string to be used as group of options in help messages
491 *
492 * @param message Group name (e.g. "RPC server options:")
493 * @return the formatted string
494 */
495 std::string HelpMessageGroup(const std::string& message);
496
497 /**
498 * Format a string to be used as option description in help messages
499 *
500 * @param option Option message (e.g. "-rpcuser=<user>")
501 * @param message Option description (e.g. "Username for JSON-RPC connections")
502 * @return the formatted string
503 */
504 std::string HelpMessageOpt(const std::string& option, const std::string& message);
505
506 namespace common {
507 #ifdef WIN32
508 class WinCmdLineArgs
509 {
510 public:
511 WinCmdLineArgs();
512 ~WinCmdLineArgs();
513 std::pair<int, char**> get();
514
515 private:
516 int argc;
517 char** argv;
518 std::vector<std::string> args;
519 };
520 #endif
521 } // namespace common
522
523 #endif // LIMENKA_COMMON_ARGS_H
524