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 | |
14 | using namespace std::string_literals; |
15 | |
16 | static 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 | |
28 | static 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 | |
53 | static 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 | |
94 | static 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 | |
127 | std::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 | |
150 | void 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 | |