1#include "global.hpp"
2#include "unit/mount.hpp"
3#include "unit/path.hpp"
4#include "unit/service.hpp"
5#include "unit/symlink.hpp"
6#include "unit/target.hpp"
7#include "unit/unit.hpp"
8
9#include <filesystem>
10#include <glob.h>
11#include <iostream>
12#include <ranges>
13
14using namespace std::string_literals;
15
16static std::vector<std::string> expand_glob(const std::string &pattern)
17{
18 glob_t glob_result;
19 glob(pattern.c_str(), 0, nullptr, &glob_result);
20
21 std::vector<std::string> paths;
22 for (size_t i = 0; i < glob_result.gl_pathc; ++i)
23 paths.push_back(x: glob_result.gl_pathv[i]);
24 globfree(&glob_result);
25 return paths;
26}
27
28static std::vector<std::string> expand_includes(const toml::node_view<toml::node> &node)
29{
30 std::vector<std::string> includes;
31 if (node.is_array())
32 {
33 if (!node.is_homogeneous())
34 std::cerr << "non-string elements in include array, they will be ignored" << std::endl;
35
36 includes = *node.as_array() | //
37 std::views::filter([](const toml::node &v) { return v.is_string(); }) | //
38 std::views::transform([](const toml::node &v) { return v.as_string()->get(); }) | //
39 std::ranges::to<std::vector>();
40 }
41 else if (node.is_string())
42 {
43 includes.push_back(x: node.as_string()->get());
44 }
45 else
46 {
47 std::cerr << "bad include paths, expect string or array but got " << node.type() << std::endl;
48 }
49
50 return includes | std::views::transform(expand_glob) | std::views::join | std::ranges::to<std::vector>();
51}
52
53static std::unique_ptr<Unit> create_unit(const std::string &id, const toml::table *data)
54{
55 if (!data || !data->is_table() || data->empty() || !data->contains(key: "type"))
56 {
57 std::cerr << "bad unit, expect table with type" << std::endl;
58 return nullptr;
59 }
60
61 const auto type = (*data)["type"];
62 if (!type.is_string())
63 {
64 std::cerr << "bad type, expect string" << std::endl;
65 return nullptr;
66 }
67
68 const auto type_string = type.as_string()->get();
69
70 std::unique_ptr<Unit> unit;
71 if (type_string == "service")
72 unit = std::make_unique<Service>(args: id);
73 else if (type_string == "target")
74 unit = std::make_unique<Target>(args: id);
75 else if (type_string == "path")
76 unit = std::make_unique<Path>(args: id);
77 else if (type_string == "mount")
78 unit = std::make_unique<Mount>(args: id);
79 else if (type_string == "symlink")
80 unit = std::make_unique<Symlink>(args: id);
81 else
82 std::cerr << "unknown type " << type_string << std::endl;
83
84 if (!unit)
85 {
86 std::cerr << "failed to create unit" << std::endl;
87 return nullptr;
88 }
89
90 unit->load(data: *data);
91 return unit;
92}
93
94static std::map<std::string, std::shared_ptr<Unit>> parse_units(const std::map<std::string, toml::table> &files)
95{
96 std::map<std::string, std::shared_ptr<Unit>> units;
97 for (const auto &[path, table] : files)
98 {
99 for (const auto &[key, value] : table)
100 {
101 const auto table = value.as_table();
102 if (!table)
103 {
104 std::cerr << "bad table" << std::endl;
105 continue;
106 }
107
108 for (const auto &[subkey, real_table] : *table)
109 {
110 const auto unit_id = key.data() + "."s + subkey.data();
111 if (units.contains(x: unit_id))
112 {
113 std::cerr << "unit " << unit_id << " already exists" << std::endl;
114 continue;
115 }
116
117 auto unit = create_unit(id: unit_id, data: real_table.as_table());
118 if (unit)
119 units[unit_id] = std::move(unit);
120 }
121 }
122 }
123
124 return units;
125}
126
127std::map<std::string, toml::table> read_all_config(const std::filesystem::path &config_path)
128{
129 toml::table main_table = toml::parse_file(file_path: config_path.string());
130 std::map<std::string, toml::table> unit_configurations;
131
132 {
133 const auto old_path = std::filesystem::current_path();
134 std::filesystem::current_path(p: config_path.parent_path());
135
136 for (const auto &include_path : expand_includes(node: main_table["include"]))
137 unit_configurations[include_path] = toml::parse_file(file_path: include_path);
138
139 main_table.erase(key: "include");
140
141 std::filesystem::current_path(p: old_path);
142 }
143
144 global_config.parse(data&: main_table); // parse global config, will remove it from main_table
145
146 unit_configurations[config_path.string()] = main_table;
147 return unit_configurations;
148}
149
150void load_configurations(const std::filesystem::path &config_path)
151{
152 units = parse_units(files: read_all_config(config_path));
153
154 // now resolve dependencies
155 for (const auto &[id, unit] : units)
156 {
157 for (const auto &dep_id : unit->depends_on)
158 {
159 if (!units.contains(x: dep_id))
160 {
161 std::cerr << "unit " << id << " depends on non-existent unit " << dep_id << std::endl;
162 continue;
163 }
164 }
165
166 for (const auto &part_id : unit->part_of)
167 {
168 // add unit to part_of unit's depends_on
169 if (!units.contains(x: part_id))
170 {
171 std::cerr << "unit " << id << " is part of non-existent unit " << part_id << std::endl;
172 continue;
173 }
174
175 units[part_id]->depends_on.push_back(x: id);
176 }
177 }
178}
179