libos+init: Add a standard API for config file access
All checks were successful
Build and test / build (push) Successful in 1m50s
All checks were successful
Build and test / build (push) Successful in 1m50s
This commit is contained in:
parent
a11aa7a2d0
commit
7c0ff8c75a
@ -20,6 +20,7 @@ set(SOURCES
|
|||||||
src/IPC.cpp
|
src/IPC.cpp
|
||||||
src/SharedMemory.cpp
|
src/SharedMemory.cpp
|
||||||
src/Timer.cpp
|
src/Timer.cpp
|
||||||
|
src/Config.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(os ${SOURCES})
|
add_library(os ${SOURCES})
|
||||||
|
43
libos/include/os/Config.h
Normal file
43
libos/include/os/Config.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* @file Config.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Configuration file parsing.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <luna/HashMap.h>
|
||||||
|
#include <luna/RefString.h>
|
||||||
|
#include <os/File.h>
|
||||||
|
#include <os/Path.h>
|
||||||
|
|
||||||
|
namespace os
|
||||||
|
{
|
||||||
|
class ConfigFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Result<OwnedPtr<ConfigFile>> open(os::Path path);
|
||||||
|
|
||||||
|
Option<StringView> read_string(StringView key);
|
||||||
|
StringView read_string_or(StringView key, StringView default_value);
|
||||||
|
|
||||||
|
Option<i64> read_number(StringView key);
|
||||||
|
i64 read_number_or(StringView key, i64 default_value);
|
||||||
|
|
||||||
|
Option<bool> read_boolean(StringView key);
|
||||||
|
bool read_boolean_or(StringView key, bool default_value);
|
||||||
|
|
||||||
|
Result<void> write_string(StringView key, StringView value);
|
||||||
|
Result<void> write_number(StringView key, int value);
|
||||||
|
Result<void> write_boolean(StringView key, bool value);
|
||||||
|
|
||||||
|
Result<void> sync();
|
||||||
|
|
||||||
|
private:
|
||||||
|
os::Path m_path { "" };
|
||||||
|
|
||||||
|
HashMap<RefString, RefString> m_data;
|
||||||
|
};
|
||||||
|
}
|
139
libos/src/Config.cpp
Normal file
139
libos/src/Config.cpp
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* @file Config.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Configuration file parsing.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <os/Config.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
namespace os
|
||||||
|
{
|
||||||
|
Result<OwnedPtr<ConfigFile>> ConfigFile::open(os::Path path)
|
||||||
|
{
|
||||||
|
auto config = TRY(make_owned<ConfigFile>());
|
||||||
|
|
||||||
|
config->m_path = path;
|
||||||
|
|
||||||
|
auto file = TRY(os::File::open(path, os::File::ReadOnly));
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto line = TRY(file->read_line());
|
||||||
|
if (line.is_empty()) break;
|
||||||
|
|
||||||
|
line.trim("\n");
|
||||||
|
if (line.is_empty()) continue;
|
||||||
|
|
||||||
|
auto parts = TRY(line.split_once('='));
|
||||||
|
if (parts.size() < 2 || parts[0].is_empty() || parts[1].is_empty()) return err(EINVAL);
|
||||||
|
|
||||||
|
auto key = TRY(RefString::from_string(parts[0]));
|
||||||
|
auto value = TRY(RefString::from_string(parts[1]));
|
||||||
|
|
||||||
|
config->m_data.try_set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<StringView> ConfigFile::read_string(StringView key)
|
||||||
|
{
|
||||||
|
auto string = RefString::from_string_view(key).release_value();
|
||||||
|
auto* value = m_data.try_get_ref(string);
|
||||||
|
|
||||||
|
if (!value) return {};
|
||||||
|
|
||||||
|
return value->view();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringView ConfigFile::read_string_or(StringView key, StringView default_value)
|
||||||
|
{
|
||||||
|
return read_string(key).value_or(default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<i64> ConfigFile::read_number(StringView key)
|
||||||
|
{
|
||||||
|
auto maybe_string = read_string(key);
|
||||||
|
|
||||||
|
if (!maybe_string.has_value()) return {};
|
||||||
|
|
||||||
|
auto string = maybe_string.value();
|
||||||
|
|
||||||
|
char* endptr;
|
||||||
|
|
||||||
|
i64 result = strtol(string.chars(), &endptr, 10);
|
||||||
|
|
||||||
|
if (string.chars() == endptr) return {};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
i64 ConfigFile::read_number_or(StringView key, i64 default_value)
|
||||||
|
{
|
||||||
|
return read_number(key).value_or(default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<bool> ConfigFile::read_boolean(StringView key)
|
||||||
|
{
|
||||||
|
auto maybe_string = read_string(key);
|
||||||
|
|
||||||
|
if (!maybe_string.has_value()) return {};
|
||||||
|
|
||||||
|
auto string = maybe_string.value();
|
||||||
|
|
||||||
|
if (string == "true" || string.to_uint().value_or(0) == 1) return true;
|
||||||
|
if (string == "false" || string.to_uint().value_or(1) == 0) return false;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigFile::read_boolean_or(StringView key, bool default_value)
|
||||||
|
{
|
||||||
|
return read_boolean(key).value_or(default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> ConfigFile::write_string(StringView key, StringView value)
|
||||||
|
{
|
||||||
|
auto key_string = TRY(RefString::from_string_view(key));
|
||||||
|
auto value_string = TRY(RefString::from_string_view(value));
|
||||||
|
|
||||||
|
auto worked = TRY(m_data.try_set(key_string, value_string));
|
||||||
|
if (!worked)
|
||||||
|
{
|
||||||
|
m_data.try_remove(key_string);
|
||||||
|
check(TRY(m_data.try_set(key_string, value_string)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> ConfigFile::write_number(StringView key, int value)
|
||||||
|
{
|
||||||
|
auto value_string = TRY(String::format("%d"_sv, value));
|
||||||
|
|
||||||
|
return write_string(key, value_string.view());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> ConfigFile::write_boolean(StringView key, bool value)
|
||||||
|
{
|
||||||
|
StringView value_string = value ? "true" : "false";
|
||||||
|
|
||||||
|
return write_string(key, value_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> ConfigFile::sync()
|
||||||
|
{
|
||||||
|
auto file = TRY(os::File::open(m_path, os::File::WriteOnly));
|
||||||
|
|
||||||
|
for (auto& pair : m_data)
|
||||||
|
{
|
||||||
|
file->write_formatted("%s=%s\n", pair.key.chars(), pair.value.release_value().chars());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
143
system/init.cpp
143
system/init.cpp
@ -6,6 +6,7 @@
|
|||||||
#include <luna/Utf8.h>
|
#include <luna/Utf8.h>
|
||||||
#include <luna/Vector.h>
|
#include <luna/Vector.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
|
#include <os/Config.h>
|
||||||
#include <os/Directory.h>
|
#include <os/Directory.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
#include <os/IPC.h>
|
#include <os/IPC.h>
|
||||||
@ -191,129 +192,49 @@ static Result<void> load_service(const os::Path& path)
|
|||||||
{
|
{
|
||||||
do_log("[init] reading service file: %s\n", path.name().chars());
|
do_log("[init] reading service file: %s\n", path.name().chars());
|
||||||
|
|
||||||
auto file = TRY(os::File::open(path, os::File::ReadOnly));
|
auto file = TRY(os::ConfigFile::open(path));
|
||||||
|
|
||||||
Service service;
|
Service service;
|
||||||
|
|
||||||
while (true)
|
auto name = file->read_string("Name");
|
||||||
{
|
if (!name.has_value())
|
||||||
auto line = TRY(file->read_line());
|
|
||||||
if (line.is_empty()) break;
|
|
||||||
|
|
||||||
line.trim("\n");
|
|
||||||
if (line.is_empty()) continue;
|
|
||||||
|
|
||||||
auto parts = TRY(line.split_once('='));
|
|
||||||
if (parts.size() < 2 || parts[0].is_empty() || parts[1].is_empty())
|
|
||||||
{
|
|
||||||
do_log("[init] file contains invalid line, aborting: '%s'\n", line.chars());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "Name")
|
|
||||||
{
|
|
||||||
service.name = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "Description")
|
|
||||||
{
|
|
||||||
// We let users specify this in the config file, but init doesn't actually use it.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "Command")
|
|
||||||
{
|
|
||||||
service.command = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "Restart")
|
|
||||||
{
|
|
||||||
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
|
|
||||||
{
|
|
||||||
service.restart = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
service.restart = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "Environment")
|
|
||||||
{
|
|
||||||
service.environment = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "StandardOutput")
|
|
||||||
{
|
|
||||||
service.standard_output = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "StandardError")
|
|
||||||
{
|
|
||||||
service.standard_error = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "StandardInput")
|
|
||||||
{
|
|
||||||
service.standard_input = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "WorkingDirectory")
|
|
||||||
{
|
|
||||||
service.working_directory = move(parts[1]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_is_system && parts[0].view() == "User")
|
|
||||||
{
|
|
||||||
auto* pw = getpwnam(parts[1].chars());
|
|
||||||
if (!pw) continue;
|
|
||||||
service.user = pw->pw_uid;
|
|
||||||
service.group = pw->pw_gid;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "Wait")
|
|
||||||
{
|
|
||||||
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
|
|
||||||
{
|
|
||||||
service.wait = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
service.wait = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts[0].view() == "WaitUntilReady")
|
|
||||||
{
|
|
||||||
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
|
|
||||||
{
|
|
||||||
service.wait_notify = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
service.wait_notify = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service.name.is_empty())
|
|
||||||
{
|
{
|
||||||
do_log("[init] service file is missing 'Name' entry, aborting!\n");
|
do_log("[init] service file is missing 'Name' entry, aborting!\n");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
service.name = TRY(String::from_string_view(name.value()));
|
||||||
|
|
||||||
if (service.command.is_empty())
|
auto command = file->read_string("Command");
|
||||||
|
if (!command.has_value())
|
||||||
{
|
{
|
||||||
do_log("[init] service file is missing 'Command' entry, aborting!\n");
|
do_log("[init] service file is missing 'Command' entry, aborting!\n");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
service.command = TRY(String::from_string_view(command.value()));
|
||||||
|
|
||||||
|
service.restart = file->read_boolean_or("Restart", false);
|
||||||
|
service.environment = TRY(String::from_string_view(file->read_string_or("Environment", {})));
|
||||||
|
service.standard_output = TRY(String::from_string_view(file->read_string_or("StandardOutput", {})));
|
||||||
|
service.standard_error = TRY(String::from_string_view(file->read_string_or("StandardError", {})));
|
||||||
|
service.standard_input = TRY(String::from_string_view(file->read_string_or("StandardInput", {})));
|
||||||
|
service.working_directory = TRY(String::from_string_view(file->read_string_or("WorkingDirectory", {})));
|
||||||
|
|
||||||
|
if (g_is_system)
|
||||||
|
{
|
||||||
|
auto user = file->read_string("User");
|
||||||
|
if (user.has_value())
|
||||||
|
{
|
||||||
|
auto* pw = getpwnam(user.value().chars());
|
||||||
|
if (pw)
|
||||||
|
{
|
||||||
|
service.user = pw->pw_uid;
|
||||||
|
service.group = pw->pw_gid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service.wait = file->read_boolean_or("Wait", false);
|
||||||
|
service.wait_notify = file->read_boolean_or("WaitUntilReady", false);
|
||||||
|
|
||||||
do_log("[init] loaded service %s into memory\n", service.name.chars());
|
do_log("[init] loaded service %s into memory\n", service.name.chars());
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user