228 const fs::path& payload_relative,
229 const fs::path& record_relative,
230 const std::string& record_json,
231 bool force =
false)
const {
236 if (!initialized.ok)
return initialized;
238 if (!lock.
acquired())
return failure(
Error::busy,
"another package operation owns this NAH root");
239 const auto recovered = recover_locked();
240 if (!recovered.ok)
return recovered;
243 if (!fs::is_directory(source, ec))
return failure(
Error::io_error,
"installation source is not a directory");
244 for (fs::recursive_directory_iterator it(source, fs::directory_options::none, ec), end;
245 !ec && it != end; it.increment(ec)) {
246 const auto status = it->symlink_status(ec);
248 if (fs::is_symlink(status) || (!fs::is_directory(status) && !fs::is_regular_file(status))) {
250 it->path().string());
253 if (ec)
return failure(
Error::io_error,
"cannot inspect installation source: " + ec.message());
254 const auto final_path = root_ / payload_relative;
255 const auto record_path = root_ / record_relative;
256 if (!force && (fs::exists(final_path, ec) || fs::exists(record_path, ec))) {
261 const auto operation = root_ /
"staging" / operation_id;
262 const auto staged_payload = operation /
"payload";
263 const auto staged_record = operation /
"record.json";
264 fs::create_directories(operation, ec);
265 fs::copy(source, staged_payload, fs::copy_options::recursive, ec);
266 if (ec)
return cleanup_failure(operation,
"cannot stage package: " + ec.message());
268 if (!
detail::sync_tree(staged_payload, error))
return cleanup_failure(operation, error);
269 if (!
detail::write_file(staged_record, record_json, error))
return cleanup_failure(operation, error);
271 nlohmann::json journal{{
"version", 1}, {
"kind",
"install"}, {
"phase",
"prepared"},
272 {
"operation", operation_id},
273 {
"payload", payload_relative.generic_string()},
274 {
"record", record_relative.generic_string()}};
275 if (!write_journal(journal, error))
return cleanup_failure(operation, error);
277 fs::create_directories(final_path.parent_path(), ec);
278 if (ec)
return rollback_failure(journal,
"cannot create payload directory: " + ec.message());
279 fs::create_directories(record_path.parent_path(), ec);
280 if (ec)
return rollback_failure(journal,
"cannot create registry directory: " + ec.message());
282 return rollback_failure(journal, error);
283 if (ec)
return rollback_failure(journal,
"cannot inspect existing payload: " + ec.message());
284 if (fs::exists(record_path, ec) && !
detail::rename_durable(record_path, operation /
"old-record.json", error))
285 return rollback_failure(journal, error);
286 if (ec)
return rollback_failure(journal,
"cannot inspect existing record: " + ec.message());
287 journal[
"phase"] =
"backed_up";
288 if (!write_journal(journal, error))
return rollback_failure(journal, error);
291 journal[
"phase"] =
"payload_active";
292 if (!write_journal(journal, error))
return rollback_failure(journal, error);
295 journal[
"phase"] =
"committed";
296 if (!write_journal(journal, error))
return rollback_failure(journal, error);
297 return finish_committed(operation);
300 Result remove(
const fs::path& payload_relative,
const fs::path& record_relative)
const {
305 if (!initialized.ok)
return initialized;
307 if (!lock.
acquired())
return failure(
Error::busy,
"another package operation owns this NAH root");
308 const auto recovered = recover_locked();
309 if (!recovered.ok)
return recovered;
312 const auto operation = root_ /
"staging" / operation_id;
313 const auto final_path = root_ / payload_relative;
314 const auto record_path = root_ / record_relative;
316 fs::create_directories(operation, ec);
317 if (ec)
return failure(
Error::io_error,
"cannot stage removal: " + ec.message());
318 nlohmann::json journal{{
"version", 1}, {
"kind",
"remove"}, {
"phase",
"prepared"},
319 {
"operation", operation_id},
320 {
"payload", payload_relative.generic_string()},
321 {
"record", record_relative.generic_string()}};
323 if (!write_journal(journal, error))
return cleanup_failure(operation, error);
325 return rollback_failure(journal, error);
326 if (ec)
return rollback_failure(journal,
"cannot inspect installed payload: " + ec.message());
327 if (fs::exists(record_path, ec) && !
detail::rename_durable(record_path, operation /
"old-record.json", error))
328 return rollback_failure(journal, error);
329 if (ec)
return rollback_failure(journal,
"cannot inspect registry record: " + ec.message());
330 journal[
"phase"] =
"committed";
331 if (!write_journal(journal, error))
return rollback_failure(journal, error);
332 return finish_committed(operation);