Add a local YAML config file for persistent settings

Reads $XDG_CONFIG_HOME/slocker-lite/config.yaml (falling back to
$HOME/.config/slocker-lite/config.yaml), organized into sections. Only
the "global" section's log-level is supported for now -- other options
are one-shot flags, not standing preferences. An explicit --log-level
on the command line always overrides the config file, the same way
SPDLOG_LEVEL already does.

Uses libyaml directly (yaml_dep was already declared in meson.build but
unused). A missing config file isn't an error; unknown sections/keys
are ignored for forward-compatibility; malformed YAML syntax is a hard
error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-21 12:31:40 +00:00
parent f7e7faed19
commit be5a0b8202
6 changed files with 173 additions and 1 deletions
+95
View File
@@ -0,0 +1,95 @@
// Copyright (C) 2026 Viorel Munteanu
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include "config_file.h"
#include <cstdlib>
#include <string_view>
#include <spdlog/spdlog.h>
#include <yaml.h>
namespace {
std::string_view scalar_value(const yaml_node_t& node) {
return {reinterpret_cast<const char*>(node.data.scalar.value), node.data.scalar.length};
}
// Finds `key` in a YAML_MAPPING_NODE and returns its value node, or nullptr if
// `node` isn't a mapping or has no such (scalar) key.
const yaml_node_t* find_in_mapping(yaml_document_t& document, const yaml_node_t& node,
std::string_view key) {
if (node.type != YAML_MAPPING_NODE) {
return nullptr;
}
for (auto* pair = node.data.mapping.pairs.start; pair < node.data.mapping.pairs.top; ++pair) {
yaml_node_t* key_node = yaml_document_get_node(&document, pair->key);
if (key_node && key_node->type == YAML_SCALAR_NODE && scalar_value(*key_node) == key) {
return yaml_document_get_node(&document, pair->value);
}
}
return nullptr;
}
} // namespace
std::filesystem::path config_file_path() {
const char* xdg_config_home = std::getenv("XDG_CONFIG_HOME");
std::filesystem::path config_home;
if (xdg_config_home && *xdg_config_home) {
config_home = xdg_config_home;
} else {
const char* home = std::getenv("HOME");
config_home = std::filesystem::path(home ? home : "") / ".config";
}
return config_home / "slocker-lite" / "config.yaml";
}
std::optional<AppConfig> load_config_file(const std::filesystem::path& path) {
FILE* file = std::fopen(path.c_str(), "r");
if (!file) {
return AppConfig{};
}
yaml_parser_t parser;
yaml_parser_initialize(&parser);
yaml_parser_set_input_file(&parser, file);
yaml_document_t document;
bool loaded = yaml_parser_load(&parser, &document) != 0;
yaml_parser_delete(&parser);
std::fclose(file);
if (!loaded) {
spdlog::error("failed to parse config file {}", path.string());
return std::nullopt;
}
AppConfig config;
yaml_node_t* root = yaml_document_get_root_node(&document);
if (root) {
if (const yaml_node_t* global = find_in_mapping(document, *root, "global")) {
if (const yaml_node_t* log_level = find_in_mapping(document, *global, "log-level")) {
if (log_level->type == YAML_SCALAR_NODE) {
config.log_level = std::string(scalar_value(*log_level));
}
}
}
}
yaml_document_delete(&document);
return config;
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (C) 2026 Viorel Munteanu
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include <filesystem>
#include <optional>
#include <string>
// Fields that make sense to persist across invocations (one-shot flags like
// -m/-r/--user don't belong here). Only the "global" section's log-level is
// supported today; add more optional fields as more long options gain config-file
// support.
struct AppConfig {
std::optional<std::string> log_level; // global.log-level
};
// $XDG_CONFIG_HOME/slocker-lite/config.yaml, or $HOME/.config/slocker-lite/config.yaml
// if XDG_CONFIG_HOME is unset/empty.
std::filesystem::path config_file_path();
// Loads and parses `path`'s "global" section. A missing file is not an error --
// returns a default-constructed AppConfig (nothing set). Unknown sections/keys are
// ignored, so the format stays forward-compatible. Malformed YAML syntax logs a
// specific error and returns nullopt.
std::optional<AppConfig> load_config_file(const std::filesystem::path& path);
+9
View File
@@ -31,6 +31,7 @@
#include "bwrap.h"
#include "config.h"
#include "config_file.h"
#include "containers_storage.h"
#include "oci_image.h"
#include "process.h"
@@ -311,6 +312,14 @@ int run_container(const std::filesystem::path& image_tar,
int main(int argc, char* argv[]) {
spdlog::cfg::load_env_levels();
auto config = load_config_file(config_file_path());
if (!config) {
return 1;
}
if (config->log_level) {
apply_log_level(*config->log_level);
}
Mode mode = Mode::kNone;
std::string mode_arg;
bool disable_nsenter = false;