FlatImage
A configurable Linux containerization system
Loading...
Searching...
No Matches
layers.hpp
Go to the documentation of this file.
1
8
9#pragma once
10
11#include <algorithm>
12#include <filesystem>
13#include <regex>
14#include <unistd.h>
15#include <iostream>
16#include <format>
17
19#include "../../lib/env.hpp"
22
23namespace
24{
25
26namespace fs = std::filesystem;
27
28}
29
38namespace ns_layers
39{
40
50[[nodiscard]] inline Value<void> create(fs::path const& path_dir_src
51 , fs::path const& path_file_dst
52 , fs::path const& path_file_list
53 , uint64_t compression_level)
54{
55 // Find mkdwarfs binary
56 auto path_file_mkdwarfs = Pop(ns_env::search_path("mkdwarfs"));
57 // Compression level
58 return_if(compression_level > 9, Error("E::Out-of-bounds compression level '{}'", compression_level));
59 // Search for all viable files to compress
60 logger("I::Gathering files to compress...");
61 std::ofstream file_list(path_file_list, std::ios::out | std::ios::trunc);
62 return_if(not file_list.is_open()
63 , Error("E::Could not open list of files '{}' to compress", path_file_list)
64 );
65 // Check if source directory exists and is a directory
66 return_if(not fs::exists(path_dir_src), Error("E::Source directory '{}' does not exist", path_dir_src));
67 return_if(not fs::is_directory(path_dir_src), Error("E::Source '{}' is not a directory", path_dir_src));
68 // Gather files to compress
69 for(auto&& entry = fs::recursive_directory_iterator(path_dir_src)
70 ; entry != fs::recursive_directory_iterator()
71 ; ++entry)
72 {
73 // Get full file path to entry
74 fs::path path_entry = entry->path();
75 // Regular file or symlink
76 if(entry->is_regular_file() or entry->is_symlink())
77 {
78 file_list
79 << path_entry.lexically_relative(path_dir_src).string()
80 << '\n';
81 }
82 // Is a directory
83 else if(entry->is_directory())
84 {
85 // Ignore directory as it is not traverseable
86 if(::access(path_entry.c_str(), R_OK | X_OK) != 0)
87 {
88 logger("I::Insufficient permissions to enter directory '{}'", path_entry);
89 entry.disable_recursion_pending();
90 continue;
91 }
92 // Add empty directory to the list
93 if(fs::is_empty(path_entry))
94 {
95 file_list
96 << path_entry.lexically_relative(path_dir_src).string()
97 << '\n';
98 }
99 }
100 // Unsupported for compression
101 else
102 {
103 logger("I::Ignoring file '{}'", path_entry);
104 }
105 }
106 file_list.close();
107
108 // Compress filesystem
109 logger("I::Compression level: '{}'", compression_level);
110 logger("I::Compress filesystem to '{}'", path_file_dst);
111 Pop(ns_subprocess::Subprocess(path_file_mkdwarfs)
112 .with_args("-f")
113 .with_args("-i", path_dir_src, "-o", path_file_dst)
114 .with_args("-l", compression_level)
115 .with_args("--input-list", path_file_list)
116 .spawn()->wait());
117 return{};
118}
119
127[[nodiscard]] inline Value<void> add(fs::path const& path_file_binary, fs::path const& path_file_layer)
128{
129 // Open binary file for writing
130 std::ofstream file_binary(path_file_binary, std::ios::app | std::ios::binary);
131 return_if(not file_binary.is_open(), Error("E::Failed to open output file '{}'", path_file_binary));
132 std::ifstream file_layer(path_file_layer, std::ios::in | std::ios::binary);
133 return_if(not file_layer.is_open(), Error("E::Failed to open input file '{}'", path_file_layer));
134 // Get byte size
135 uint64_t file_size = Try(fs::file_size(path_file_layer));
136 // Write byte size
137 file_binary.write(reinterpret_cast<char*>(&file_size), sizeof(file_size));
138 char buff[8192];
139 while( file_layer.read(buff, sizeof(buff)) or file_layer.gcount() > 0 )
140 {
141 file_binary.write(buff, file_layer.gcount());
142 return_if(not file_binary, Error("E::Error writing data to file"));
143 }
144 logger("I::Included novel layer from file '{}'", path_file_layer);
145 return {};
146}
147
154[[nodiscard]] inline Value<uint64_t> find_next_layer_number(fs::path const& path_dir_layers)
155{
156 // Check if layers directory is accessible
157 if (not Try(fs::exists(path_dir_layers)) or not Try(fs::is_directory(path_dir_layers)))
158 {
159 return Error("E::Layers directory is missing or not a directory");
160 }
161 // Get current layers
162 std::regex regex_layer(R"(layer-[0-9]+\.layer)");
163 auto vec = Try(fs::directory_iterator(path_dir_layers)
164 | std::views::filter([](auto&& e){ return e.is_regular_file(); })
165 | std::views::transform([](auto&& e){ return e.path().filename().string(); })
166 | std::views::filter([&](auto&& e){ return std::regex_search(e, regex_layer); })
167 | std::views::transform([](auto&& e){ return std::stoi(e.substr(6, e.substr(6).find('.'))); })
168 | std::ranges::to<std::vector<int>>()
169 );
170 // Get max layer number
171 int next = (vec.empty())? 0 : *std::ranges::max_element(vec) + 1;
172 return_if(next > 999, Error("E::Maximum number of layers exceeded"));
173 return next;
174}
175
189enum class CommitMode { BINARY, LAYER, FILE };
190
191[[nodiscard]] inline Value<void> commit(
192 fs::path const& path_file_binary
193 , fs::path const& path_dir_src
194 , fs::path const& path_file_layer_tmp
195 , fs::path const& path_file_list_tmp
196 , uint32_t layer_compression_level
197 , CommitMode mode
198 , std::optional<fs::path> const& path_dst = std::nullopt)
199{
200 // Create filesystem based on the contents of src
201 Pop(ns_layers::create(path_dir_src
202 , path_file_layer_tmp
203 , path_file_list_tmp
204 , layer_compression_level
205 ));
206
207 // Handle the layer based on the commit mode
208 switch(mode)
209 {
210 case CommitMode::BINARY:
211 {
212 // Include filesystem in the image
213 Pop(add(path_file_binary, path_file_layer_tmp));
214 // Remove layer file
215 if(not Try(fs::remove(path_file_layer_tmp)))
216 {
217 logger("E::Could not erase layer file '{}'", path_file_layer_tmp.string());
218 }
219 logger("I::Filesystem appended to binary without errors");
220 }
221 break;
222 case CommitMode::LAYER:
223 {
224 return_if(not path_dst.has_value(), Error("E::Layer mode requires a destination directory"));
225 return_if(not Try(fs::is_directory(path_dst.value())), Error("E::Destination should be a directory"));
226 // Find next available layer number
227 uint64_t layer_num = Pop(find_next_layer_number(path_dst.value()));
228 fs::path layer_path = path_dst.value() / std::format("layer-{:03d}.layer", layer_num);
229 logger("I::Layer number: {}", layer_num);
230 logger("I::Layer path: {}", layer_path);
231 // Move layer file to layers directory
232 Try(fs::rename(path_file_layer_tmp, layer_path));
233 logger("I::Layer saved to '{}'", layer_path.string());
234 }
235 break;
236 case CommitMode::FILE:
237 {
238 return_if(not path_dst.has_value(), Error("E::File mode requires a destination path"));
239 return_if(Try(fs::exists(path_dst.value())), Error("E::Destination file already exists"));
240 // Move layer file to destination
241 Try(fs::rename(path_file_layer_tmp, path_dst.value()));
242 logger("I::Layer saved to '{}'", path_dst.value().string());
243 }
244 break;
245 }
246 // Remove files from the compression list
247 std::ifstream file_list(path_file_list_tmp);
248 return_if(not file_list.is_open(), Error("E::Could not open file list for erasing files..."));
249 std::string line;
250 // getline doesn't throw by default, no need to wrap
251 while(std::getline(file_list, line))
252 {
253 fs::path path_file_target = path_dir_src / line;
254 fs::path path_dir_parent = path_file_target.parent_path();
255 // Remove target file (with error_code - doesn't throw)
256 if(not Try(fs::remove(path_file_target)))
257 {
258 logger("E::Could not remove file {}", path_file_target.string());
259 }
260 // Remove empty directory (with error_code - doesn't throw)
261 if(Try(fs::is_empty(path_dir_parent)) and not Try(fs::remove(path_dir_parent)))
262 {
263 logger("E::Could not remove directory {}", path_dir_parent.string());
264 }
265 }
266 return {};
267}
268
274inline void list(ns_filesystems::ns_layers::Layers const& layers)
275{
276 for(uint64_t index = 0; auto const& layer : layers.get_layers())
277 {
278 std::cout << std::format("{}:{}:{}:{}\n", index, layer.offset, layer.size, layer.path.string());
279 ++index;
280 }
281}
282
283} // namespace ns_layers
284
285/* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
Manages external DwarFS layer files and directories for the filesystem controller.
Definition layers.hpp:60
std::vector< Layer > const & get_layers() const
Retrieves the collected layer file paths with offsets.
Definition layers.hpp:174
Enhanced error handling framework built on std::expected.
Layer management for DwarFS filesystems.
A library for manipulating environment variables.
#define logger(fmt,...)
Compile-time log level dispatch macro with automatic location capture.
Definition log.hpp:682
Value< fs::path > search_path(std::string const &query)
Search the directories in the PATH variable for the given input file name.
Definition env.hpp:150
Filesystem layer management command implementation.
CommitMode
Commit changes into a novel layer (binary/layer/file modes)
Definition layers.hpp:189
Value< void > add(fs::path const &path_file_binary, fs::path const &path_file_layer)
Includes a filesystem in the target FlatImage.
Definition layers.hpp:127
Value< uint64_t > find_next_layer_number(fs::path const &path_dir_layers)
Finds the next available layer number in the layers directory.
Definition layers.hpp:154
Value< void > create(fs::path const &path_dir_src, fs::path const &path_file_dst, fs::path const &path_file_list, uint64_t compression_level)
Creates a layer (filesystem) from a source directory.
Definition layers.hpp:50
void list(ns_filesystems::ns_layers::Layers const &layers)
Lists all layers in the format index:offset:size:path.
Definition layers.hpp:274
Enhanced expected type with integrated logging capabilities.
Definition expected.hpp:44
A library to spawn sub-processes in linux.