1 /**
2  * Copyright (c) 2013, Timothy Stack
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  * * Neither the name of Timothy Stack nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * @file lnav_config.cc
30  */
31 
32 #include "config.h"
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <glob.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40 #include <unistd.h>
41 #include <libgen.h>
42 
43 #include <regex>
44 #include <chrono>
45 #include <iostream>
46 #include <stdexcept>
47 #include <fmt/format.h>
48 
49 #include "auto_fd.hh"
50 #include "base/injector.hh"
51 #include "base/injector.bind.hh"
52 #include "base/paths.hh"
53 #include "base/string_util.hh"
54 #include "base/lnav_log.hh"
55 #include "lnav_util.hh"
56 #include "auto_mem.hh"
57 #include "base/auto_pid.hh"
58 #include "lnav_config.hh"
59 #include "yajlpp/yajlpp.hh"
60 #include "yajlpp/yajlpp_def.hh"
61 #include "styling.hh"
62 #include "bin2c.hh"
63 #include "default-config.h"
64 
65 using namespace std;
66 
67 static const int MAX_CRASH_LOG_COUNT = 16;
68 static const auto STDIN_CAPTURE_RETENTION = 24h;
69 
70 static auto intern_lifetime = intern_string::get_table_lifetime();
71 
72 struct _lnav_config lnav_config;
73 struct _lnav_config rollback_lnav_config;
74 static struct _lnav_config lnav_default_config;
75 
76 std::map<intern_string_t, source_location> lnav_config_locations;
77 
78 lnav_config_listener *lnav_config_listener::LISTENER_LIST;
79 
__anon470316650102() 80 static auto a = injector::bind<archive_manager::config>::to_instance(+[]() {
81     return &lnav_config.lc_archive_manager;
82 });
83 
__anon470316650202() 84 static auto fvc = injector::bind<file_vtab::config>::to_instance(+[]() {
85     return &lnav_config.lc_file_vtab;
86 });
87 
__anon470316650302() 88 static auto lc = injector::bind<lnav::logfile::config>::to_instance(+[]() {
89     return &lnav_config.lc_logfile;
90 });
91 
__anon470316650402() 92 static auto tc = injector::bind<tailer::config>::to_instance(+[]() {
93     return &lnav_config.lc_tailer;
94 });
95 
__anon470316650502() 96 static auto scc = injector::bind<sysclip::config>::to_instance(+[]() {
97     return &lnav_config.lc_sysclip;
98 });
99 
check_experimental(const char * feature_name)100 bool check_experimental(const char *feature_name)
101 {
102     const char *env_value = getenv("LNAV_EXP");
103 
104     require(feature_name != nullptr);
105 
106     if (env_value && strcasestr(env_value, feature_name)) {
107         return true;
108     }
109 
110     return false;
111 }
112 
ensure_dotlnav()113 void ensure_dotlnav()
114 {
115     static const char *subdirs[] = {
116         "",
117         "configs",
118         "configs/default",
119         "configs/installed",
120         "formats",
121         "formats/default",
122         "formats/installed",
123         "staging",
124         "stdin-captures",
125         "crash",
126     };
127 
128     auto path = lnav::paths::dotlnav();
129 
130     for (auto sub_path : subdirs) {
131         auto full_path = path / sub_path;
132 
133         log_perror(mkdir(full_path.c_str(), 0755));
134     }
135 
136     lnav_log_crash_dir = strdup(path.c_str());
137 
138     {
139         static_root_mem<glob_t, globfree> gl;
140         auto crash_glob = path / "crash/*";
141 
142         if (glob(crash_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
143             for (int lpc = 0;
144                  lpc < ((int)gl->gl_pathc - MAX_CRASH_LOG_COUNT);
145                  lpc++) {
146                 log_perror(remove(gl->gl_pathv[lpc]));
147             }
148         }
149     }
150 
151     {
152         static_root_mem<glob_t, globfree> gl;
153         auto cap_glob = path / "stdin-captures/*";
154 
155         if (glob(cap_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
156             auto old_time = std::chrono::system_clock::now() -
157                 STDIN_CAPTURE_RETENTION;
158 
159             for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
160                 struct stat st;
161 
162                 if (stat(gl->gl_pathv[lpc], &st) == -1) {
163                     continue;
164                 }
165 
166                 if (chrono::system_clock::from_time_t(st.st_mtime) > old_time) {
167                     continue;
168                 }
169 
170                 log_debug("Removing old stdin capture: %s", gl->gl_pathv[lpc]);
171                 log_perror(remove(gl->gl_pathv[lpc]));
172             }
173         }
174     }
175 }
176 
install_from_git(const char * repo)177 bool install_from_git(const char *repo)
178 {
179     static const std::regex repo_name_converter("[^\\w]");
180 
181     auto formats_path = lnav::paths::dotlnav() / "formats";
182     auto configs_path = lnav::paths::dotlnav() / "configs";
183     auto staging_path = lnav::paths::dotlnav() / "staging";
184     string local_name = std::regex_replace(repo, repo_name_converter, "_");
185 
186     auto local_formats_path = formats_path / local_name;
187     auto local_configs_path = configs_path / local_name;
188     auto local_staging_path = staging_path / local_name;
189 
190     auto fork_res = lnav::pid::from_fork();
191     if (fork_res.isErr()) {
192         fprintf(stderr, "error: cannot fork() to run git: %s\n",
193                 fork_res.unwrapErr().c_str());
194         _exit(1);
195     }
196 
197     auto git_cmd = fork_res.unwrap();
198     if (git_cmd.in_child()) {
199         if (ghc::filesystem::is_directory(local_formats_path)) {
200             printf("Updating format repo: %s\n", repo);
201             log_perror(chdir(local_formats_path.c_str()));
202             execlp("git", "git", "pull", nullptr);
203         }
204         else if (ghc::filesystem::is_directory(local_configs_path)) {
205             printf("Updating config repo: %s\n", repo);
206             log_perror(chdir(local_configs_path.c_str()));
207             execlp("git", "git", "pull", nullptr);
208         }
209         else {
210             execlp("git", "git", "clone", repo, local_staging_path.c_str(), nullptr);
211         }
212         _exit(1);
213     }
214 
215     auto finished_child = std::move(git_cmd).wait_for_child();
216 
217     if (!finished_child.was_normal_exit() ||
218         finished_child.exit_status() != 0) {
219         return false;
220     }
221 
222     if (ghc::filesystem::is_directory(local_staging_path)) {
223         auto config_path = local_staging_path / "*.json";
224         static_root_mem<glob_t, globfree> gl;
225         bool found_config_file = false, found_format_file = false;
226 
227         if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
228             for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
229                 auto json_file_path = gl->gl_pathv[lpc];
230                 auto file_type_result = detect_config_file_type(json_file_path);
231 
232                 if (file_type_result.isErr()) {
233                     fprintf(stderr, "error: %s\n",
234                             file_type_result.unwrapErr().c_str());
235                     return false;
236                 }
237                 if (file_type_result.unwrap() == config_file_type::CONFIG) {
238                     found_config_file = true;
239                 } else {
240                     found_format_file = true;
241                 }
242             }
243         }
244 
245         if (found_config_file) {
246             rename(local_staging_path.c_str(),
247                    local_configs_path.c_str());
248             fprintf(stderr, "info: installed configuration repo -- %s\n",
249                     local_configs_path.c_str());
250         } else if (found_format_file) {
251             rename(local_staging_path.c_str(),
252                    local_formats_path.c_str());
253             fprintf(stderr, "info: installed format repo -- %s\n",
254                     local_formats_path.c_str());
255         } else {
256             fprintf(stderr, "error: cannot find a valid lnav configuration or format file\n");
257             return false;
258         }
259     }
260 
261     return true;
262 }
263 
update_installs_from_git()264 bool update_installs_from_git()
265 {
266     static_root_mem<glob_t, globfree> gl;
267     auto git_formats = lnav::paths::dotlnav() / "formats/*/.git";
268     bool found = false, retval = true;
269 
270     if (glob(git_formats.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
271         for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
272             char *git_dir = dirname(gl->gl_pathv[lpc]);
273             char pull_cmd[1024];
274 
275             printf("Updating formats in %s\n", git_dir);
276             snprintf(pull_cmd, sizeof(pull_cmd),
277                      "cd %s && git pull",
278                      git_dir);
279             int ret = system(pull_cmd);
280             if (ret == -1) {
281                 std::cerr << "Failed to spawn command "
282                           << "\"" << pull_cmd << "\": "
283                           << strerror(errno) << std::endl;
284                 retval = false;
285             }
286             else if (ret > 0) {
287                 std::cerr << "Command "
288                           << "\"" << pull_cmd << "\" failed: "
289                           << strerror(errno) << std::endl;
290                 retval = false;
291             }
292             found = true;
293         }
294     }
295 
296     if (!found) {
297         printf("No formats from git repositories found, "
298                "use 'lnav -i extra' to install third-party foramts\n");
299     }
300 
301     return retval;
302 }
303 
read_repo_path(yajlpp_parse_context * ypc,const unsigned char * str,size_t len)304 static int read_repo_path(yajlpp_parse_context *ypc, const unsigned char *str, size_t len)
305 {
306     string path = string((const char *)str, len);
307 
308     install_from_git(path.c_str());
309 
310     return 1;
311 }
312 
313 static struct json_path_container format_handlers = {
314     json_path_handler("format-repos#", read_repo_path)
315 };
316 
install_extra_formats()317 void install_extra_formats()
318 {
319     auto config_root = lnav::paths::dotlnav() / "remote-config";
320     auto_fd fd;
321 
322     if (access(config_root.c_str(), R_OK) == 0) {
323         char pull_cmd[1024];
324 
325         printf("Updating lnav remote config repo...\n");
326         snprintf(pull_cmd, sizeof(pull_cmd),
327                  "cd '%s' && git pull",
328                  config_root.c_str());
329         log_perror(system(pull_cmd));
330     }
331     else {
332         char clone_cmd[1024];
333 
334         printf("Cloning lnav remote config repo...\n");
335         snprintf(clone_cmd, sizeof(clone_cmd),
336                  "git clone https://github.com/tstack/lnav-config.git %s",
337                  config_root.c_str());
338         log_perror(system(clone_cmd));
339     }
340 
341     auto config_json = config_root / "remote-config.json";
342     if ((fd = openp(config_json, O_RDONLY)) == -1) {
343         perror("Unable to open remote-config.json");
344     }
345     else {
346         yajlpp_parse_context ypc_config(config_root.string(), &format_handlers);
347         auto_mem<yajl_handle_t> jhandle(yajl_free);
348         unsigned char buffer[4096];
349         ssize_t rc;
350 
351         jhandle = yajl_alloc(&ypc_config.ypc_callbacks, nullptr, &ypc_config);
352         yajl_config(jhandle, yajl_allow_comments, 1);
353         while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
354             if (yajl_parse(jhandle,
355                            buffer,
356                            rc) != yajl_status_ok) {
357                 auto msg = yajl_get_error(jhandle, 1, buffer, rc);
358                 fprintf(stderr,
359                         "Unable to parse remote-config.json -- %s",
360                         msg);
361                 yajl_free_error(jhandle, msg);
362                 return;
363             }
364         }
365         if (yajl_complete_parse(jhandle) != yajl_status_ok) {
366             auto msg = yajl_get_error(jhandle, 1, buffer, rc);
367 
368             fprintf(stderr,
369                     "Unable to parse remote-config.json -- %s",
370                     msg);
371             yajl_free_error(jhandle, msg);
372         }
373     }
374 }
375 
376 struct userdata {
userdatauserdata377     userdata(vector<string> &errors) : ud_errors(errors) {};
378 
379     vector<string> &ud_errors;
380 };
381 
config_error_reporter(const yajlpp_parse_context & ypc,lnav_log_level_t level,const char * msg)382 static void config_error_reporter(const yajlpp_parse_context &ypc,
383                                   lnav_log_level_t level,
384                                   const char *msg)
385 {
386     if (level >= lnav_log_level_t::ERROR) {
387         struct userdata *ud = (userdata *) ypc.ypc_userdata;
388 
389         ud->ud_errors.emplace_back(msg);
390     } else {
391         fprintf(stderr, "warning:%s\n",  msg);
392     }
393 }
394 
395 static struct json_path_container key_command_handlers = {
396     yajlpp::property_handler("command")
397         .with_synopsis("<command>")
398         .with_description(
399             "The command to execute for the given key sequence.  Use a script "
400             "to execute more complicated operations.")
401         .with_pattern("[:|;].*")
402         .with_example(":goto next hour")
403         .FOR_FIELD(key_command, kc_cmd),
404     yajlpp::property_handler("alt-msg")
405         .with_synopsis("<msg>")
406         .with_description("The help message to display after the key is pressed.")
407         .FOR_FIELD(key_command, kc_alt_msg)
408 };
409 
410 static struct json_path_container keymap_def_handlers = {
411     yajlpp::pattern_property_handler("(?<key_seq>(?:x[0-9a-f]{2})+)")
412         .with_synopsis("<utf8-key-code-in-hex>")
413         .with_description(
414             "Map of key codes to commands to execute.  The field names are "
415             "the keys to be mapped using as a hexadecimal representation of "
416             "the UTF-8 encoding.  Each byte of the UTF-8 should start with "
417             "an 'x' followed by the hexadecimal representation of the byte.")
__anon470316650602(const yajlpp_provider_context &ypc, key_map *km) 418         .with_obj_provider<key_command, key_map>([](const yajlpp_provider_context &ypc, key_map *km) {
419             key_command &retval = km->km_seq_to_cmd[ypc.ypc_extractor.get_substr("key_seq")];
420 
421             return &retval;
422         })
__anon470316650702(key_map *km, vector<string> &paths_out) 423         .with_path_provider<key_map>([](key_map *km, vector<string> &paths_out) {
424             for (const auto &iter : km->km_seq_to_cmd) {
425                 paths_out.emplace_back(iter.first);
426             }
427         })
428         .with_children(key_command_handlers)
429 };
430 
431 static struct json_path_container keymap_defs_handlers = {
432     yajlpp::pattern_property_handler("(?<keymap_name>[\\w\\-]+)")
433         .with_description("The keymap definitions")
__anon470316650802(const yajlpp_provider_context &ypc, _lnav_config *root) 434         .with_obj_provider<key_map, _lnav_config>([](const yajlpp_provider_context &ypc, _lnav_config *root) {
435             key_map &retval = root->lc_ui_keymaps[ypc.ypc_extractor.get_substr("keymap_name")];
436             return &retval;
437         })
__anon470316650902(struct _lnav_config *cfg, vector<string> &paths_out) 438         .with_path_provider<_lnav_config>([](struct _lnav_config *cfg, vector<string> &paths_out) {
439             for (const auto &iter : cfg->lc_ui_keymaps) {
440                 paths_out.emplace_back(iter.first);
441             }
442         })
443         .with_children(keymap_def_handlers)
444 };
445 
446 static struct json_path_container global_var_handlers = {
447     yajlpp::pattern_property_handler("(?<var_name>\\w+)")
448         .with_synopsis("<name>")
449         .with_description(
450             "A global variable definition.  Global variables can be referenced "
451             "in scripts, SQL statements, or commands.")
452         .with_path_provider<_lnav_config>(
__anon470316650a02(struct _lnav_config *cfg, vector<string> &paths_out) 453             [](struct _lnav_config *cfg, vector<string> &paths_out) {
454               for (const auto &iter : cfg->lc_global_vars) {
455                 paths_out.emplace_back(iter.first);
456               }
457             })
458         .FOR_FIELD(_lnav_config, lc_global_vars)
459 };
460 
461 static struct json_path_container style_config_handlers =
462     json_path_container{
463         yajlpp::property_handler("color")
464             .with_synopsis("#hex|color_name")
465             .with_description(
466                 "The foreground color value for this style. The value can be "
467                 "the name of an xterm color, the hexadecimal value, or a theme "
468                 "variable reference.")
469             .with_example("#fff")
470             .with_example("Green")
471             .with_example("$black")
472             .FOR_FIELD(style_config, sc_color),
473         yajlpp::property_handler("background-color")
474             .with_synopsis("#hex|color_name")
475             .with_description(
476                 "The background color value for this style. The value can be "
477                 "the name of an xterm color, the hexadecimal value, or a theme "
478                 "variable reference.")
479             .with_example("#2d2a2e")
480             .with_example("Green")
481             .FOR_FIELD(style_config, sc_background_color),
482         yajlpp::property_handler("underline")
483             .with_description("Indicates that the text should be underlined.")
484             .FOR_FIELD(style_config, sc_underline),
485         yajlpp::property_handler("bold")
486             .with_description("Indicates that the text should be bolded.")
487             .FOR_FIELD(style_config, sc_bold),
488     }
489         .with_definition_id("style");
490 
491 static struct json_path_container theme_styles_handlers = {
492     yajlpp::property_handler("identifier")
493         .with_description("Styling for identifiers in logs")
__anon470316650b02(const yajlpp_provider_context &ypc, lnav_theme *root) 494         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
495             return &root->lt_style_identifier;
496         })
497         .with_children(style_config_handlers),
498     yajlpp::property_handler("text")
499         .with_description("Styling for plain text")
__anon470316650c02(const yajlpp_provider_context &ypc, lnav_theme *root) 500         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
501             return &root->lt_style_text;
502         })
503         .with_children(style_config_handlers),
504     yajlpp::property_handler("alt-text")
505         .with_description("Styling for plain text when alternating")
__anon470316650d02(const yajlpp_provider_context &ypc, lnav_theme *root) 506         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
507             return &root->lt_style_alt_text;
508         })
509         .with_children(style_config_handlers),
510     yajlpp::property_handler("error")
511         .with_description("Styling for error messages")
__anon470316650e02(const yajlpp_provider_context &ypc, lnav_theme *root) 512         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
513             return &root->lt_style_error;
514         })
515         .with_children(style_config_handlers),
516     yajlpp::property_handler("ok")
517         .with_description("Styling for success messages")
__anon470316650f02(const yajlpp_provider_context &ypc, lnav_theme *root) 518         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
519             return &root->lt_style_ok;
520         })
521         .with_children(style_config_handlers),
522     yajlpp::property_handler("warning")
523         .with_description("Styling for warning messages")
__anon470316651002(const yajlpp_provider_context &ypc, lnav_theme *root) 524         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
525             return &root->lt_style_warning;
526         })
527         .with_children(style_config_handlers),
528     yajlpp::property_handler("hidden")
529         .with_description("Styling for hidden fields in logs")
__anon470316651102(const yajlpp_provider_context &ypc, lnav_theme *root) 530         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
531             return &root->lt_style_hidden;
532         })
533         .with_children(style_config_handlers),
534     yajlpp::property_handler("adjusted-time")
535         .with_description("Styling for timestamps that have been adjusted")
__anon470316651202(const yajlpp_provider_context &ypc, lnav_theme *root) 536         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
537             return &root->lt_style_adjusted_time;
538         })
539         .with_children(style_config_handlers),
540     yajlpp::property_handler("skewed-time")
541         .with_description("Styling for timestamps that are different from the received time")
__anon470316651302(const yajlpp_provider_context &ypc, lnav_theme *root) 542         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
543             return &root->lt_style_skewed_time;
544         })
545         .with_children(style_config_handlers),
546     yajlpp::property_handler("offset-time")
547         .with_description("Styling for hidden fields")
__anon470316651402(const yajlpp_provider_context &ypc, lnav_theme *root) 548         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
549             return &root->lt_style_offset_time;
550         })
551         .with_children(style_config_handlers),
552     yajlpp::property_handler("invalid-msg")
553         .with_description("Styling for invalid log messages")
__anon470316651502(const yajlpp_provider_context &ypc, lnav_theme *root) 554         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
555             return &root->lt_style_invalid_msg;
556         })
557         .with_children(style_config_handlers),
558     yajlpp::property_handler("popup")
559         .with_description("Styling for popup windows")
__anon470316651602(const yajlpp_provider_context &ypc, lnav_theme *root) 560         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
561             return &root->lt_style_popup;
562         })
563         .with_children(style_config_handlers),
564     yajlpp::property_handler("focused")
565         .with_description("Styling for a focused row in a list view")
__anon470316651702(const yajlpp_provider_context &ypc, lnav_theme *root) 566         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
567             return &root->lt_style_focused;
568         })
569         .with_children(style_config_handlers),
570     yajlpp::property_handler("disabled-focused")
571         .with_description("Styling for a disabled focused row in a list view")
__anon470316651802(const yajlpp_provider_context &ypc, lnav_theme *root) 572         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
573             return &root->lt_style_disabled_focused;
574         })
575         .with_children(style_config_handlers),
576     yajlpp::property_handler("scrollbar")
577         .with_description("Styling for scrollbars")
__anon470316651902(const yajlpp_provider_context &ypc, lnav_theme *root) 578         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
579             return &root->lt_style_scrollbar;
580         })
581         .with_children(style_config_handlers)
582 };
583 
584 static struct json_path_container theme_syntax_styles_handlers = {
585     yajlpp::property_handler("keyword")
586         .with_description("Styling for keywords in source files")
__anon470316651a02(const yajlpp_provider_context &ypc, lnav_theme *root) 587         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
588             return &root->lt_style_keyword;
589         })
590         .with_children(style_config_handlers),
591     yajlpp::property_handler("string")
592         .with_description("Styling for single/double-quoted strings in text")
__anon470316651b02(const yajlpp_provider_context &ypc, lnav_theme *root) 593         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
594             return &root->lt_style_string;
595         })
596         .with_children(style_config_handlers),
597     yajlpp::property_handler("comment")
598         .with_description("Styling for comments in source files")
__anon470316651c02(const yajlpp_provider_context &ypc, lnav_theme *root) 599         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
600             return &root->lt_style_comment;
601         })
602         .with_children(style_config_handlers),
603     yajlpp::property_handler("doc-directive")
604         .with_description("Styling for documentation directives in source files")
__anon470316651d02(const yajlpp_provider_context &ypc, lnav_theme *root) 605         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
606             return &root->lt_style_doc_directive;
607         })
608         .with_children(style_config_handlers),
609     yajlpp::property_handler("variable")
610         .with_description("Styling for variables in text")
__anon470316651e02(const yajlpp_provider_context &ypc, lnav_theme *root) 611         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
612             return &root->lt_style_variable;
613         })
614         .with_children(style_config_handlers),
615     yajlpp::property_handler("symbol")
616         .with_description("Styling for symbols in source files")
__anon470316651f02(const yajlpp_provider_context &ypc, lnav_theme *root) 617         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
618             return &root->lt_style_symbol;
619         })
620         .with_children(style_config_handlers),
621     yajlpp::property_handler("number")
622         .with_description("Styling for numbers in source files")
__anon470316652002(const yajlpp_provider_context &ypc, lnav_theme *root) 623         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
624             return &root->lt_style_number;
625         })
626         .with_children(style_config_handlers),
627     yajlpp::property_handler("re-special")
628         .with_description("Styling for special characters in regular expressions")
__anon470316652102(const yajlpp_provider_context &ypc, lnav_theme *root) 629         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
630             return &root->lt_style_re_special;
631         })
632         .with_children(style_config_handlers),
633     yajlpp::property_handler("re-repeat")
634         .with_description("Styling for repeats in regular expressions")
__anon470316652202(const yajlpp_provider_context &ypc, lnav_theme *root) 635         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
636             return &root->lt_style_re_repeat;
637         })
638         .with_children(style_config_handlers),
639 
640     yajlpp::property_handler("diff-delete")
641         .with_description("Styling for deleted lines in diffs")
__anon470316652302(const yajlpp_provider_context &ypc, lnav_theme *root) 642         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
643             return &root->lt_style_diff_delete;
644         })
645         .with_children(style_config_handlers),
646     yajlpp::property_handler("diff-add")
647         .with_description("Styling for added lines in diffs")
__anon470316652402(const yajlpp_provider_context &ypc, lnav_theme *root) 648         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
649             return &root->lt_style_diff_add;
650         })
651         .with_children(style_config_handlers),
652     yajlpp::property_handler("diff-section")
653         .with_description("Styling for diffs")
__anon470316652502(const yajlpp_provider_context &ypc, lnav_theme *root) 654         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
655             return &root->lt_style_diff_section;
656         })
657         .with_children(style_config_handlers),
658     yajlpp::property_handler("file")
659         .with_description("Styling for file names in source files")
__anon470316652602(const yajlpp_provider_context &ypc, lnav_theme *root) 660         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
661             return &root->lt_style_file;
662         })
663         .with_children(style_config_handlers)
664 };
665 
666 static struct json_path_container theme_status_styles_handlers = {
667     yajlpp::property_handler("text")
668         .with_description("Styling for status bars")
__anon470316652702(const yajlpp_provider_context &ypc, lnav_theme *root) 669         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
670             return &root->lt_style_status;
671         })
672         .with_children(style_config_handlers),
673     yajlpp::property_handler("warn")
674         .with_description("Styling for warnings in status bars")
__anon470316652802(const yajlpp_provider_context &ypc, lnav_theme *root) 675         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
676             return &root->lt_style_warn_status;
677         })
678         .with_children(style_config_handlers),
679     yajlpp::property_handler("alert")
680         .with_description("Styling for alerts in status bars")
__anon470316652902(const yajlpp_provider_context &ypc, lnav_theme *root) 681         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
682             return &root->lt_style_alert_status;
683         })
684         .with_children(style_config_handlers),
685     yajlpp::property_handler("active")
686         .with_description("Styling for activity in status bars")
__anon470316652a02(const yajlpp_provider_context &ypc, lnav_theme *root) 687         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
688             return &root->lt_style_active_status;
689         })
690         .with_children(style_config_handlers),
691     yajlpp::property_handler("inactive-alert")
692         .with_description("Styling for inactive alert status bars")
__anon470316652b02(const yajlpp_provider_context &ypc, lnav_theme *root) 693         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
694             return &root->lt_style_inactive_alert_status;
695         })
696         .with_children(style_config_handlers),
697     yajlpp::property_handler("inactive")
698         .with_description("Styling for inactive status bars")
__anon470316652c02(const yajlpp_provider_context &ypc, lnav_theme *root) 699         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
700             return &root->lt_style_inactive_status;
701         })
702         .with_children(style_config_handlers),
703     yajlpp::property_handler("title-hotkey")
704         .with_description("Styling for hotkey highlights in titles")
__anon470316652d02(const yajlpp_provider_context &ypc, lnav_theme *root) 705         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
706             return &root->lt_style_status_title_hotkey;
707         })
708         .with_children(style_config_handlers),
709     yajlpp::property_handler("title")
710         .with_description("Styling for title sections of status bars")
__anon470316652e02(const yajlpp_provider_context &ypc, lnav_theme *root) 711         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
712             return &root->lt_style_status_title;
713         })
714         .with_children(style_config_handlers),
715     yajlpp::property_handler("disabled-title")
716         .with_description("Styling for title sections of status bars")
__anon470316652f02(const yajlpp_provider_context &ypc, lnav_theme *root) 717         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
718             return &root->lt_style_status_disabled_title;
719         })
720         .with_children(style_config_handlers),
721     yajlpp::property_handler("subtitle")
722         .with_description("Styling for subtitle sections of status bars")
__anon470316653002(const yajlpp_provider_context &ypc, lnav_theme *root) 723         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
724             return &root->lt_style_status_subtitle;
725         })
726         .with_children(style_config_handlers),
727     yajlpp::property_handler("hotkey")
728         .with_description("Styling for hotkey highlights of status bars")
__anon470316653102(const yajlpp_provider_context &ypc, lnav_theme *root) 729         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
730             return &root->lt_style_status_hotkey;
731         })
732         .with_children(style_config_handlers),
733 };
734 
735 static struct json_path_container theme_log_level_styles_handlers = {
736     yajlpp::pattern_property_handler("(?<level>trace|debug5|debug4|debug3|debug2|debug|info|stats|notice|warning|error|critical|fatal|invalid)")
__anon470316653202(const yajlpp_provider_context &ypc, lnav_theme *root) 737         .with_obj_provider<style_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
738             style_config &sc = root->lt_level_styles[
739                 string2level(ypc.ypc_extractor.get_substr_i("level").get())];
740 
741             return &sc;
742         })
__anon470316653302(struct lnav_theme *cfg, vector<string> &paths_out) 743         .with_path_provider<lnav_theme>([](struct lnav_theme *cfg, vector<string> &paths_out) {
744             for (int lpc = LEVEL_TRACE; lpc < LEVEL__MAX; lpc++) {
745                 paths_out.emplace_back(level_names[lpc]);
746             }
747         })
748         .with_children(style_config_handlers)
749 };
750 
751 static struct json_path_container highlighter_handlers = {
752     yajlpp::property_handler("pattern")
753         .with_synopsis("regular expression")
754         .with_description("The regular expression to highlight")
755         .FOR_FIELD(highlighter_config, hc_regex),
756 
757     yajlpp::property_handler("style")
758         .with_description("The styling for the text that matches the associated pattern")
__anon470316653402(const yajlpp_provider_context &ypc, highlighter_config *root) 759         .with_obj_provider<style_config, highlighter_config>([](const yajlpp_provider_context &ypc, highlighter_config *root) {
760             return &root->hc_style;
761         })
762         .with_children(style_config_handlers),
763 };
764 
765 static struct json_path_container theme_highlights_handlers = {
766     yajlpp::pattern_property_handler("(?<highlight_name>\\w+)")
__anon470316653502(const yajlpp_provider_context &ypc, lnav_theme *root) 767         .with_obj_provider<highlighter_config, lnav_theme>([](const yajlpp_provider_context &ypc, lnav_theme *root) {
768             highlighter_config &hc = root->lt_highlights[
769                 ypc.ypc_extractor.get_substr_i("highlight_name").get()];
770 
771             return &hc;
772         })
__anon470316653602(struct lnav_theme *cfg, vector<string> &paths_out) 773         .with_path_provider<lnav_theme>([](struct lnav_theme *cfg, vector<string> &paths_out) {
774             for (const auto& pair : cfg->lt_highlights) {
775                 paths_out.emplace_back(pair.first);
776             }
777         })
778         .with_children(highlighter_handlers)
779 };
780 
781 static struct json_path_container theme_vars_handlers = {
782     yajlpp::pattern_property_handler("(?<var_name>\\w+)")
783         .with_synopsis("name")
784         .with_description("A theme variable definition")
__anon470316653702(struct lnav_theme *lt, vector<string> &paths_out) 785         .with_path_provider<lnav_theme>([](struct lnav_theme *lt, vector<string> &paths_out) {
786             for (const auto &iter : lt->lt_vars) {
787                 paths_out.emplace_back(iter.first);
788             }
789         })
790         .FOR_FIELD(lnav_theme, lt_vars)
791 };
792 
793 static struct json_path_container theme_def_handlers = {
794     yajlpp::property_handler("vars")
795         .with_description("Variables definitions that are used in this theme.")
796         .with_children(theme_vars_handlers),
797 
798     yajlpp::property_handler("styles")
799         .with_description("Styles for log messages.")
800         .with_children(theme_styles_handlers),
801 
802     yajlpp::property_handler("syntax-styles")
803         .with_description("Styles for syntax highlighting in text files.")
804         .with_children(theme_syntax_styles_handlers),
805 
806     yajlpp::property_handler("status-styles")
807         .with_description("Styles for the user-interface components.")
808         .with_children(theme_status_styles_handlers),
809 
810     yajlpp::property_handler("log-level-styles")
811         .with_description("Styles for each log message level.")
812         .with_children(theme_log_level_styles_handlers),
813 
814     yajlpp::property_handler("highlights")
815         .with_description("Styles for text highlights.")
816         .with_children(theme_highlights_handlers),
817 };
818 
819 static struct json_path_container theme_defs_handlers = {
820     yajlpp::pattern_property_handler("(?<theme_name>[\\w\\-]+)")
821         .with_description("Theme definitions")
__anon470316653802(const yajlpp_provider_context &ypc, _lnav_config *root) 822         .with_obj_provider<lnav_theme, _lnav_config>([](const yajlpp_provider_context &ypc, _lnav_config *root) {
823             lnav_theme &lt = root->lc_ui_theme_defs[ypc.ypc_extractor.get_substr("theme_name")];
824 
825             return &lt;
826         })
__anon470316653902(struct _lnav_config *cfg, vector<string> &paths_out) 827         .with_path_provider<_lnav_config>([](struct _lnav_config *cfg, vector<string> &paths_out) {
828             for (const auto &iter : cfg->lc_ui_theme_defs) {
829                 paths_out.emplace_back(iter.first);
830             }
831         })
832         .with_children(theme_def_handlers)
833 };
834 
835 static struct json_path_container ui_handlers = {
836     yajlpp::property_handler("clock-format")
837         .with_synopsis("format")
838         .with_description("The format for the clock displayed in "
839                           "the top-left corner using strftime(3) conversions")
840         .with_example("%a %b %d %H:%M:%S %Z")
841         .FOR_FIELD(_lnav_config, lc_ui_clock_format),
842     yajlpp::property_handler("dim-text")
843         .with_synopsis("bool")
844         .with_description("Reduce the brightness of text (useful for xterms). "
845                           "This setting can be useful when running in an xterm "
846                           "where the white color is very bright.")
847         .FOR_FIELD(_lnav_config, lc_ui_dim_text),
848     yajlpp::property_handler("default-colors")
849         .with_synopsis("bool")
850         .with_description(
851             "Use default terminal background and foreground colors "
852             "instead of black and white for all text coloring.  This setting "
853             "can be useful when transparent background or alternate color "
854             "theme terminal is used.")
855         .FOR_FIELD(_lnav_config, lc_ui_default_colors),
856     yajlpp::property_handler("keymap")
857         .with_synopsis("keymap_name")
858         .with_description("The name of the keymap to use.")
859         .FOR_FIELD(_lnav_config, lc_ui_keymap),
860     yajlpp::property_handler("theme")
861         .with_synopsis("theme_name")
862         .with_description("The name of the theme to use.")
863         .FOR_FIELD(_lnav_config, lc_ui_theme),
864     yajlpp::property_handler("theme-defs")
865         .with_description("Theme definitions.")
866         .with_children(theme_defs_handlers),
867     yajlpp::property_handler("keymap-defs")
868         .with_description("Keymap definitions.")
869         .with_children(keymap_defs_handlers),
870 };
871 
872 static struct json_path_container archive_handlers = {
873     yajlpp::property_handler("min-free-space")
874         .with_synopsis("<bytes>")
875         .with_description(
876             "The minimum free space, in bytes, to maintain when unpacking "
877             "archives")
878         .with_min_value(0)
879         .for_field(&_lnav_config::lc_archive_manager,
880                    &archive_manager::config::amc_min_free_space),
881     yajlpp::property_handler("cache-ttl")
882         .with_synopsis("<duration>")
883         .with_description(
884             "The time-to-live for unpacked archives, expressed as a duration "
885             "(e.g. '3d' for three days)")
886         .with_example("3d")
887         .with_example("12h")
888         .for_field(&_lnav_config::lc_archive_manager,
889                    &archive_manager::config::amc_cache_ttl),
890 };
891 
892 static struct json_path_container file_vtab_handlers = {
893     yajlpp::property_handler("max-content-size")
894         .with_synopsis("<bytes>")
895         .with_description(
896             "The maximum allowed file size for the content column")
897         .with_min_value(0)
898         .for_field(&_lnav_config::lc_file_vtab,
899                    &file_vtab::config::fvc_max_content_size),
900 };
901 
902 static struct json_path_container logfile_handlers = {
903     yajlpp::property_handler("max-unrecognized-lines")
904         .with_synopsis("<lines>")
905         .with_description(
906             "The maximum number of lines in a file to use when detecting the format")
907         .with_min_value(1)
908         .for_field(&_lnav_config::lc_logfile,
909                    &lnav::logfile::config::lc_max_unrecognized_lines),
910 };
911 
912 static struct json_path_container ssh_config_handlers = {
913     yajlpp::pattern_property_handler("(?<config_name>\\w+)")
914         .with_synopsis("name")
915         .with_description("Set an SSH configuration value")
916         .with_path_provider<_lnav_config>([](
__anon470316653a02( auto *m, std::vector<std::string> &paths_out) 917             auto *m, std::vector<std::string> &paths_out) {
918             for (const auto& pair : m->lc_tailer.c_ssh_config) {
919                 paths_out.emplace_back(pair.first);
920             }
921         })
922         .for_field(&_lnav_config::lc_tailer,
923                    &tailer::config::c_ssh_config),
924 };
925 
926 static struct json_path_container ssh_option_handlers = {
927     yajlpp::pattern_property_handler("(?<option_name>\\w+)")
928         .with_synopsis("name")
929         .with_description("Set an option to be passed to the SSH command")
930         .for_field(&_lnav_config::lc_tailer,
931                    &tailer::config::c_ssh_options),
932 };
933 
934 static struct json_path_container ssh_handlers = {
935     yajlpp::property_handler("command")
936         .with_synopsis("ssh-command")
937         .with_description("The SSH command to execute")
938         .for_field(&_lnav_config::lc_tailer,
939                    &tailer::config::c_ssh_cmd),
940     yajlpp::property_handler("transfer-command")
941         .with_synopsis("command")
942         .with_description(
943             "Command executed on the remote host when transferring the file")
944         .for_field(&_lnav_config::lc_tailer,
945                    &tailer::config::c_transfer_cmd),
946     yajlpp::property_handler("start-command")
947         .with_synopsis("command")
948         .with_description(
949             "Command executed on the remote host to start the tailer")
950         .for_field(&_lnav_config::lc_tailer,
951                    &tailer::config::c_start_cmd),
952     yajlpp::property_handler("flags")
953         .with_description("The flags to pass to the SSH command")
954         .for_field(&_lnav_config::lc_tailer,
955                    &tailer::config::c_ssh_flags),
956     yajlpp::property_handler("options")
957         .with_description("The options to pass to the SSH command")
958         .with_children(ssh_option_handlers),
959     yajlpp::property_handler("config")
960         .with_description(
961             "The ssh_config options to pass to SSH with the -o option")
962         .with_children(ssh_config_handlers),
963 };
964 
965 static struct json_path_container remote_handlers = {
966     yajlpp::property_handler("cache-ttl")
967         .with_synopsis("<duration>")
968         .with_description(
969             "The time-to-live for files copied from remote hosts, expressed as a duration "
970             "(e.g. '3d' for three days)")
971         .with_example("3d")
972         .with_example("12h")
973         .for_field(&_lnav_config::lc_tailer,
974                    &tailer::config::c_cache_ttl),
975     yajlpp::property_handler("ssh")
976         .with_description(
977             "Settings related to the ssh command used to contact remote "
978             "machines")
979         .with_children(ssh_handlers),
980 };
981 
982 static struct json_path_container sysclip_impl_cmd_handlers = json_path_container{
983     yajlpp::property_handler("write")
984         .with_synopsis("<command>")
985         .with_description("The command used to write to the clipboard")
986         .with_example("pbcopy")
987         .for_field(&sysclip::clip_commands::cc_write),
988     yajlpp::property_handler("read")
989         .with_synopsis("<command>")
990         .with_description("The command used to read from the clipboard")
991         .with_example("pbpaste")
992         .for_field(&sysclip::clip_commands::cc_read),
993 }
994     .with_description("Container for the commands used to read from and write to the system clipboard")
995     .with_definition_id("clip-commands");
996 
997 static struct json_path_container sysclip_impl_handlers = {
998     yajlpp::property_handler("test")
999         .with_synopsis("<command>")
1000         .with_description("The command that checks")
1001         .with_example("command -v pbcopy")
1002         .for_field(&sysclip::clipboard::c_test_command),
1003     yajlpp::property_handler("general")
1004         .with_description("Commands to work with the general clipboard")
__anon470316653b02(const yajlpp_provider_context &ypc, sysclip::clipboard *root) 1005         .with_obj_provider<sysclip::clip_commands, sysclip::clipboard>([](const yajlpp_provider_context &ypc, sysclip::clipboard *root) {
1006             return &root->c_general;
1007         })
1008         .with_children(sysclip_impl_cmd_handlers),
1009     yajlpp::property_handler("find")
1010         .with_description("Commands to work with the find clipboard")
__anon470316653c02(const yajlpp_provider_context &ypc, sysclip::clipboard *root) 1011         .with_obj_provider<sysclip::clip_commands, sysclip::clipboard>([](const yajlpp_provider_context &ypc, sysclip::clipboard *root) {
1012             return &root->c_find;
1013         })
1014         .with_children(sysclip_impl_cmd_handlers),
1015 };
1016 
1017 static struct json_path_container sysclip_impls_handlers = {
1018     yajlpp::pattern_property_handler("(?<clipboard_impl_name>[\\w\\-]+)")
1019         .with_synopsis("<name>")
1020         .with_description("Clipboard implementation")
__anon470316653d02(const yajlpp_provider_context &ypc, _lnav_config *root) 1021         .with_obj_provider<sysclip::clipboard, _lnav_config>([](const yajlpp_provider_context &ypc, _lnav_config *root) {
1022             auto &retval = root->lc_sysclip.c_clipboard_impls[ypc.ypc_extractor.get_substr("clipboard_impl_name")];
1023             return &retval;
1024         })
__anon470316653e02(struct _lnav_config *cfg, vector<string> &paths_out) 1025         .with_path_provider<_lnav_config>([](struct _lnav_config *cfg, vector<string> &paths_out) {
1026             for (const auto &iter : cfg->lc_sysclip.c_clipboard_impls) {
1027                 paths_out.emplace_back(iter.first);
1028             }
1029         })
1030         .with_children(sysclip_impl_handlers),
1031 };
1032 
1033 static struct json_path_container sysclip_handlers = {
1034     yajlpp::property_handler("impls")
1035         .with_description("Clipboard implementations")
1036         .with_children(sysclip_impls_handlers),
1037 };
1038 
1039 static struct json_path_container tuning_handlers = {
1040     yajlpp::property_handler("archive-manager")
1041         .with_description("Settings related to opening archive files")
1042         .with_children(archive_handlers),
1043     yajlpp::property_handler("file-vtab")
1044         .with_description("Settings related to the lnav_file virtual-table")
1045         .with_children(file_vtab_handlers),
1046     yajlpp::property_handler("logfile")
1047         .with_description("Settings related to log files")
1048         .with_children(logfile_handlers),
1049     yajlpp::property_handler("remote")
1050         .with_description("Settings related to remote file support")
1051         .with_children(remote_handlers),
1052     yajlpp::property_handler("clipboard")
1053         .with_description("Settings related to the clipboard")
1054         .with_children(sysclip_handlers),
1055 };
1056 
1057 static set<string> SUPPORTED_CONFIG_SCHEMAS = {
1058     "https://lnav.org/schemas/config-v1.schema.json",
1059 };
1060 
1061 const char *DEFAULT_FORMAT_SCHEMA =
1062     "https://lnav.org/schemas/format-v1.schema.json";
1063 
1064 set<string> SUPPORTED_FORMAT_SCHEMAS = {
1065     DEFAULT_FORMAT_SCHEMA,
1066 };
1067 
read_id(yajlpp_parse_context * ypc,const unsigned char * str,size_t len)1068 static int read_id(yajlpp_parse_context *ypc, const unsigned char *str, size_t len)
1069 {
1070     auto file_id = string((const char *) str, len);
1071 
1072     if (SUPPORTED_CONFIG_SCHEMAS.count(file_id) == 0) {
1073         ypc->report_error(
1074             lnav_log_level_t::ERROR,
1075             "%s:%d: error: unsupported configuration $schema -- %s\n",
1076             ypc->ypc_source.c_str(),
1077             ypc->get_line_number(),
1078             file_id.c_str());
1079         return 0;
1080     }
1081 
1082     return 1;
1083 }
1084 
1085 struct json_path_container lnav_config_handlers = json_path_container {
1086     json_path_handler("$schema", read_id)
1087         .with_synopsis("The URI of the schema for this file")
1088         .with_description("Specifies the type of this file"),
1089 
1090     yajlpp::property_handler("tuning")
1091         .with_description("Internal settings")
1092         .with_children(tuning_handlers),
1093 
1094     yajlpp::property_handler("ui")
1095         .with_description("User-interface settings")
1096         .with_children(ui_handlers),
1097 
1098     yajlpp::property_handler("global")
1099         .with_description("Global variable definitions")
1100         .with_children(global_var_handlers)
1101 }
1102     .with_schema_id(*SUPPORTED_CONFIG_SCHEMAS.cbegin());
1103 
1104 class active_key_map_listener : public lnav_config_listener {
1105 public:
reload_config(error_reporter & reporter)1106     void reload_config(error_reporter &reporter) override
1107     {
1108         lnav_config.lc_active_keymap = lnav_config.lc_ui_keymaps["default"];
1109         for (const auto &pair :
1110              lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap].km_seq_to_cmd) {
1111             lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first] = pair.second;
1112         }
1113     }
1114 };
1115 
1116 static active_key_map_listener KEYMAP_LISTENER;
1117 
1118 Result<config_file_type, std::string>
detect_config_file_type(const ghc::filesystem::path & path)1119 detect_config_file_type(const ghc::filesystem::path &path)
1120 {
1121     static const char *id_path[] = {"$schema", nullptr};
1122 
1123     auto read_res = read_file(path);
1124 
1125     if (read_res.isErr()) {
1126         return Err(fmt::format("unable to open file: {} -- {}",
1127                                path.string(), read_res.unwrapErr()));
1128     }
1129 
1130     auto content = read_res.unwrap();
1131     if (startswith(content, "#")) {
1132         content.insert(0, "//");
1133     }
1134 
1135     char error_buffer[1024];
1136     auto content_tree = unique_ptr<yajl_val_s, decltype(&yajl_tree_free)>(
1137         yajl_tree_parse(content.c_str(), error_buffer, sizeof(error_buffer)),
1138         yajl_tree_free);
1139     if (content_tree == nullptr) {
1140         return Err(fmt::format("unable to parse file: {} -- {}",
1141             path.string(), error_buffer));
1142     }
1143 
1144     auto id_val = yajl_tree_get(content_tree.get(), id_path, yajl_t_string);
1145     if (id_val != nullptr) {
1146         if (SUPPORTED_CONFIG_SCHEMAS.count(id_val->u.string)) {
1147             return Ok(config_file_type::CONFIG);
1148         } else if (SUPPORTED_FORMAT_SCHEMAS.count(id_val->u.string)) {
1149             return Ok(config_file_type::FORMAT);
1150         } else {
1151             return Err(fmt::format("unsupported configuration version in file: {} -- {}",
1152                 path.string(), id_val->u.string));
1153         }
1154     } else {
1155         return Ok(config_file_type::FORMAT);
1156     }
1157 }
1158 
load_config_from(_lnav_config & lconfig,const ghc::filesystem::path & path,vector<string> & errors)1159 static void load_config_from(_lnav_config& lconfig, const ghc::filesystem::path &path, vector<string> &errors)
1160 {
1161     yajlpp_parse_context ypc(path.string(), &lnav_config_handlers);
1162     struct userdata ud(errors);
1163     auto_fd fd;
1164 
1165     ypc.ypc_locations = &lnav_config_locations;
1166     ypc.with_obj(lconfig);
1167     ypc.ypc_userdata = &ud;
1168     ypc.with_error_reporter(config_error_reporter);
1169     if ((fd = openp(path, O_RDONLY)) == -1) {
1170         if (errno != ENOENT) {
1171             char errmsg[1024];
1172 
1173             snprintf(errmsg, sizeof(errmsg),
1174                      "error: unable to open format file -- %s",
1175                      path.c_str());
1176             errors.emplace_back(errmsg);
1177         }
1178     }
1179     else {
1180         auto_mem<yajl_handle_t> handle(yajl_free);
1181         char buffer[2048];
1182         off_t offset = 0;
1183         ssize_t rc = -1;
1184 
1185         handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
1186         yajl_config(handle, yajl_allow_comments, 1);
1187         yajl_config(handle, yajl_allow_multiple_values, 1);
1188         ypc.ypc_handle = handle;
1189         while (true) {
1190             rc = read(fd, buffer, sizeof(buffer));
1191             if (rc == 0) {
1192                 break;
1193             }
1194             else if (rc == -1) {
1195                 errors.push_back(path.string() +
1196                                  ":unable to read file -- " +
1197                                  string(strerror(errno)));
1198                 break;
1199             }
1200             if (ypc.parse((const unsigned char *)buffer, rc) != yajl_status_ok) {
1201                 break;
1202             }
1203             offset += rc;
1204         }
1205         if (rc == 0) {
1206             ypc.complete_parse();
1207         }
1208     }
1209 }
1210 
load_default_config(struct _lnav_config & config_obj,const std::string & path,const bin_src_file & bsf,vector<string> & errors)1211 static void load_default_config(struct _lnav_config &config_obj,
1212                                 const std::string &path,
1213                                 const bin_src_file &bsf,
1214                                 vector<string> &errors)
1215 {
1216     yajlpp_parse_context ypc_builtin(bsf.get_name(), &lnav_config_handlers);
1217     auto_mem<yajl_handle_t> handle(yajl_free);
1218     struct userdata ud(errors);
1219 
1220     handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
1221     ypc_builtin.ypc_locations = &lnav_config_locations;
1222     ypc_builtin.with_handle(handle);
1223     ypc_builtin.with_obj(config_obj);
1224     ypc_builtin.with_error_reporter(config_error_reporter);
1225     ypc_builtin.ypc_userdata = &ud;
1226 
1227     if (path != "*") {
1228         ypc_builtin.ypc_ignore_unused = true;
1229         ypc_builtin.ypc_active_paths.insert(path);
1230     }
1231 
1232     yajl_config(handle, yajl_allow_comments, 1);
1233     yajl_config(handle, yajl_allow_multiple_values, 1);
1234     if (ypc_builtin.parse(bsf.to_string_fragment()) == yajl_status_ok) {
1235         ypc_builtin.complete_parse();
1236     }
1237 }
1238 
load_default_configs(struct _lnav_config & config_obj,const std::string & path,vector<string> & errors)1239 static void load_default_configs(struct _lnav_config &config_obj,
1240                                  const std::string &path,
1241                                  vector<string> &errors)
1242 {
1243     for (auto& bsf : lnav_config_json) {
1244         load_default_config(config_obj, path, bsf, errors);
1245     }
1246 }
1247 
load_config(const vector<ghc::filesystem::path> & extra_paths,vector<string> & errors)1248 void load_config(const vector<ghc::filesystem::path> &extra_paths, vector<string> &errors)
1249 {
1250     auto user_config = lnav::paths::dotlnav() / "config.json";
1251 
1252     for (auto& bsf : lnav_config_json) {
1253         auto sample_path = lnav::paths::dotlnav() /
1254                            "configs" /
1255                            "default" /
1256                            fmt::format("{}.sample", bsf.get_name());
1257 
1258         auto fd = auto_fd(openp(sample_path, O_WRONLY|O_TRUNC|O_CREAT, 0644));
1259         auto sf = bsf.to_string_fragment();
1260         if (fd == -1 || write(fd.get(), sf.data(), sf.length()) == -1) {
1261             fprintf(stderr,
1262                     "error:unable to write default config file: %s -- %s\n",
1263                     sample_path.c_str(),
1264                     strerror(errno));
1265         }
1266     }
1267 
1268     {
1269         load_default_configs(lnav_default_config, "*", errors);
1270         load_default_configs(lnav_config, "*", errors);
1271 
1272         for (const auto &extra_path : extra_paths) {
1273             auto config_path = extra_path / "configs/*/*.json";
1274             static_root_mem<glob_t, globfree> gl;
1275 
1276             if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
1277                 for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
1278                     load_config_from(lnav_config, gl->gl_pathv[lpc], errors);
1279                     if (errors.empty()) {
1280                         load_config_from(lnav_default_config, gl->gl_pathv[lpc], errors);
1281                     }
1282                 }
1283             }
1284         }
1285         for (const auto &extra_path : extra_paths) {
1286             auto config_path = extra_path / "formats/*/config.*.json";
1287             static_root_mem<glob_t, globfree> gl;
1288 
1289             if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
1290                 for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
1291                     load_config_from(lnav_config, gl->gl_pathv[lpc], errors);
1292                     if (errors.empty()) {
1293                         load_config_from(lnav_default_config, gl->gl_pathv[lpc], errors);
1294                     }
1295                 }
1296             }
1297         }
1298 
1299         load_config_from(lnav_config, user_config, errors);
1300     }
1301 
1302     reload_config(errors);
1303 
1304     rollback_lnav_config = lnav_config;
1305 }
1306 
reset_config(const std::string & path)1307 void reset_config(const std::string &path)
1308 {
1309     vector<string> errors;
1310 
1311     load_default_configs(lnav_config, path, errors);
1312 
1313     reload_config(errors);
1314 
1315     for (auto &err: errors) {
1316         log_debug("reset %s", err.c_str());
1317     }
1318 }
1319 
save_config()1320 string save_config()
1321 {
1322     yajlpp_gen gen;
1323     auto filename = fmt::format("config.json.{}.tmp", getpid());
1324     auto user_config_tmp = lnav::paths::dotlnav() / filename;
1325     auto user_config = lnav::paths::dotlnav() / "config.json";
1326 
1327     yajl_gen_config(gen, yajl_gen_beautify, true);
1328     yajlpp_gen_context ygc(gen, lnav_config_handlers);
1329     vector<string> errors;
1330 
1331     ygc.with_default_obj(lnav_default_config)
1332        .with_obj(lnav_config);
1333     ygc.gen();
1334 
1335     {
1336         auto_fd fd;
1337 
1338         if ((fd = openp(user_config_tmp,
1339                         O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1) {
1340             return "error: unable to save configuration -- " +
1341                    string(strerror(errno));
1342         } else {
1343             string_fragment bits = gen.to_string_fragment();
1344 
1345             log_perror(write(fd, bits.data(), bits.length()));
1346         }
1347     }
1348 
1349     rename(user_config_tmp.c_str(), user_config.c_str());
1350 
1351     return "info: configuration saved";
1352 }
1353 
reload_config(vector<string> & errors)1354 void reload_config(vector<string> &errors)
1355 {
1356     lnav_config_listener *curr = lnav_config_listener::LISTENER_LIST;
1357 
1358     while (curr != nullptr) {
1359         auto reporter = [&errors](const void *cfg_value, const std::string &errmsg) {
1360             auto cb = [&cfg_value, &errors, &errmsg](
1361                 const json_path_handler_base &jph,
1362                 const string &path,
1363                 void *mem) {
1364                 if (mem != cfg_value) {
1365                     return;
1366                 }
1367 
1368                 auto loc_iter = lnav_config_locations.find(intern_string::lookup(path));
1369                 if (loc_iter == lnav_config_locations.end()) {
1370                     return;
1371                 }
1372 
1373                 char msg[1024];
1374 
1375                 snprintf(msg, sizeof(msg),
1376                          "%s:%d:%s",
1377                          loc_iter->second.sl_source.get(),
1378                          loc_iter->second.sl_line_number,
1379                          errmsg.c_str());
1380 
1381                 errors.emplace_back(msg);
1382             };
1383 
1384             for (auto &jph : lnav_config_handlers.jpc_children) {
1385                 jph.walk(cb, &lnav_config);
1386             }
1387         };
1388 
1389         curr->reload_config(reporter);
1390         curr = curr->lcl_next;
1391     }
1392 }
1393