ladybird/Userland/Libraries/LibCore/ConfigFile.cpp
Tim Schumacher d5871f5717 AK: Rename Stream::{read,write} to Stream::{read_some,write_some}
Similar to POSIX read, the basic read and write functions of AK::Stream
do not have a lower limit of how much data they read or write (apart
from "none at all").

Rename the functions to "read some [data]" and "write some [data]" (with
"data" being omitted, since everything here is reading and writing data)
to make them sufficiently distinct from the functions that ensure to
use the entire buffer (which should be the go-to function for most
usages).

No functional changes, just a lot of new FIXMEs.
2023-03-13 15:16:20 +00:00

250 lines
7.8 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, networkException <networkexception@serenityos.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <AK/StringBuilder.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/Directory.h>
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <pwd.h>
#include <sys/types.h>
namespace Core {
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_lib(DeprecatedString const& lib_name, AllowWriting allow_altering)
{
DeprecatedString directory_name = DeprecatedString::formatted("{}/lib", StandardPaths::config_directory());
auto directory = TRY(Directory::create(directory_name, Directory::CreateDirectories::Yes));
auto path = DeprecatedString::formatted("{}/{}.ini", directory, lib_name);
return ConfigFile::open(path, allow_altering);
}
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_app(DeprecatedString const& app_name, AllowWriting allow_altering)
{
auto directory = TRY(Directory::create(StandardPaths::config_directory(), Directory::CreateDirectories::Yes));
auto path = DeprecatedString::formatted("{}/{}.ini", directory, app_name);
return ConfigFile::open(path, allow_altering);
}
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_system(DeprecatedString const& app_name, AllowWriting allow_altering)
{
auto path = DeprecatedString::formatted("/etc/{}.ini", app_name);
return ConfigFile::open(path, allow_altering);
}
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, AllowWriting allow_altering)
{
auto maybe_file = File::open(filename, allow_altering == AllowWriting::Yes ? File::OpenMode::ReadWrite : File::OpenMode::Read);
OwnPtr<BufferedFile> buffered_file;
if (maybe_file.is_error()) {
// If we attempted to open a read-only file that does not exist, we ignore the error, making it appear
// the same as if we had opened an empty file. This behavior is a little weird, but is required by
// user code, which does not check the config file exists before opening.
if (!(allow_altering == AllowWriting::No && maybe_file.error().code() == ENOENT))
return maybe_file.release_error();
} else {
buffered_file = TRY(BufferedFile::create(maybe_file.release_value()));
}
auto config_file = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ConfigFile(filename, move(buffered_file))));
TRY(config_file->reparse());
return config_file;
}
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, int fd)
{
auto file = TRY(File::adopt_fd(fd, File::OpenMode::ReadWrite));
return open(filename, move(file));
}
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, NonnullOwnPtr<Core::File> file)
{
auto buffered_file = TRY(BufferedFile::create(move(file)));
auto config_file = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ConfigFile(filename, move(buffered_file))));
TRY(config_file->reparse());
return config_file;
}
ConfigFile::ConfigFile(DeprecatedString const& filename, OwnPtr<BufferedFile> open_file)
: m_filename(filename)
, m_file(move(open_file))
{
}
ConfigFile::~ConfigFile()
{
MUST(sync());
}
ErrorOr<void> ConfigFile::reparse()
{
m_groups.clear();
if (!m_file)
return {};
HashMap<DeprecatedString, DeprecatedString>* current_group = nullptr;
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
while (TRY(m_file->can_read_line())) {
auto line = TRY(m_file->read_line(buffer));
size_t i = 0;
while (i < line.length() && (line[i] == ' ' || line[i] == '\t' || line[i] == '\n'))
++i;
if (i >= line.length())
continue;
switch (line[i]) {
case '#': // Comment, skip entire line.
case ';': // -||-
continue;
case '[': { // Start of new group.
StringBuilder builder;
++i; // Skip the '['
while (i < line.length() && (line[i] != ']')) {
builder.append(line[i]);
++i;
}
current_group = &m_groups.ensure(builder.to_deprecated_string());
break;
}
default: { // Start of key
StringBuilder key_builder;
StringBuilder value_builder;
while (i < line.length() && (line[i] != '=')) {
key_builder.append(line[i]);
++i;
}
++i; // Skip the '='
while (i < line.length() && (line[i] != '\n')) {
value_builder.append(line[i]);
++i;
}
if (!current_group) {
// We're not in a group yet, create one with the name ""...
current_group = &m_groups.ensure("");
}
auto value_string = value_builder.to_deprecated_string();
current_group->set(key_builder.to_deprecated_string(), value_string.trim_whitespace(TrimMode::Right));
}
}
}
return {};
}
DeprecatedString ConfigFile::read_entry(DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& default_value) const
{
if (!has_key(group, key)) {
return default_value;
}
auto it = m_groups.find(group);
auto jt = it->value.find(key);
return jt->value;
}
bool ConfigFile::read_bool_entry(DeprecatedString const& group, DeprecatedString const& key, bool default_value) const
{
auto value = read_entry(group, key, default_value ? "true" : "false");
return value == "1" || value.equals_ignoring_ascii_case("true"sv);
}
void ConfigFile::write_entry(DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
{
m_groups.ensure(group).ensure(key) = value;
m_dirty = true;
}
void ConfigFile::write_bool_entry(DeprecatedString const& group, DeprecatedString const& key, bool value)
{
write_entry(group, key, value ? "true" : "false");
}
ErrorOr<void> ConfigFile::sync()
{
if (!m_dirty)
return {};
if (!m_file)
return Error::from_errno(ENOENT);
TRY(m_file->truncate(0));
TRY(m_file->seek(0, SeekMode::SetPosition));
for (auto& it : m_groups) {
// FIXME: This should write the entire span.
TRY(m_file->write_some(DeprecatedString::formatted("[{}]\n", it.key).bytes()));
for (auto& jt : it.value)
TRY(m_file->write_some(DeprecatedString::formatted("{}={}\n", jt.key, jt.value).bytes()));
TRY(m_file->write_some("\n"sv.bytes()));
}
m_dirty = false;
return {};
}
void ConfigFile::dump() const
{
for (auto& it : m_groups) {
outln("[{}]", it.key);
for (auto& jt : it.value)
outln("{}={}", jt.key, jt.value);
outln();
}
}
Vector<DeprecatedString> ConfigFile::groups() const
{
return m_groups.keys();
}
Vector<DeprecatedString> ConfigFile::keys(DeprecatedString const& group) const
{
auto it = m_groups.find(group);
if (it == m_groups.end())
return {};
return it->value.keys();
}
bool ConfigFile::has_key(DeprecatedString const& group, DeprecatedString const& key) const
{
auto it = m_groups.find(group);
if (it == m_groups.end())
return {};
return it->value.contains(key);
}
bool ConfigFile::has_group(DeprecatedString const& group) const
{
return m_groups.contains(group);
}
void ConfigFile::add_group(DeprecatedString const& group)
{
m_groups.ensure(group);
m_dirty = true;
}
void ConfigFile::remove_group(DeprecatedString const& group)
{
m_groups.remove(group);
m_dirty = true;
}
void ConfigFile::remove_entry(DeprecatedString const& group, DeprecatedString const& key)
{
auto it = m_groups.find(group);
if (it == m_groups.end())
return;
it->value.remove(key);
m_dirty = true;
}
}