From 5f0f0b02b4f50065e666f1f0d0ab84903deb9112 Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Mon, 2 Mar 2026 23:06:55 -0500 Subject: [PATCH] Package Kit, pkgman, SoftwareUpdater: Offer to delete old state directories. This adds a new job to the Package Kit, and an invocation in pkgman after changes are applied. The job takes a time_t before, and an int32 minToKeep, and (after confirmation) deletes all state directories which were last modified before that time, but also keeps at least the minimum specified. pkgman defaults to calling it with (now - 30 days, minimum 10). SoftwareUpdater does the same, but without bothering the user and just defaults to doing the cleanup. Change-Id: I15f5232b11daba5955e7fa07f696ad7785507931 Reviewed-on: https://review.haiku-os.org/c/haiku/+/10404 Reviewed-by: waddlesplash --- .../os/package/CleanUpAdminDirectoryRequest.h | 40 ++++ src/bin/pkgman/PackageManager.cpp | 15 ++ .../package/CleanUpAdminDirectoryRequest.cpp | 215 ++++++++++++++++++ src/kits/package/Jamfile | 1 + 4 files changed, 271 insertions(+) create mode 100644 headers/os/package/CleanUpAdminDirectoryRequest.h create mode 100644 src/kits/package/CleanUpAdminDirectoryRequest.cpp diff --git a/headers/os/package/CleanUpAdminDirectoryRequest.h b/headers/os/package/CleanUpAdminDirectoryRequest.h new file mode 100644 index 0000000000..925e8611b6 --- /dev/null +++ b/headers/os/package/CleanUpAdminDirectoryRequest.h @@ -0,0 +1,40 @@ +/* + * Copyright 2026, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _PACKAGE__CLEAN_UP_ADMIN_DIR_REQUEST_H_ +#define _PACKAGE__CLEAN_UP_ADMIN_DIR_REQUEST_H_ + + +#include + +#include +#include +#include + + +namespace BPackageKit { + + +class CleanUpAdminDirectoryRequest : public BRequest { + typedef BRequest inherited; + +public: + CleanUpAdminDirectoryRequest(const BContext& context, + const BInstallationLocationInfo& location, + time_t cleanupBefore, int32 minStatesToKeep); + virtual ~CleanUpAdminDirectoryRequest(); + + virtual status_t CreateInitialJobs(); + +private: + const BInstallationLocationInfo fLocationInfo; + time_t fCleanupBefore; + int32 fMinimumStatesToKeep; +}; + + +} // namespace BPackageKit + + +#endif // _PACKAGE__CLEAN_UP_ADMIN_DIR_REQUEST_H_ diff --git a/src/bin/pkgman/PackageManager.cpp b/src/bin/pkgman/PackageManager.cpp index 2560f351fc..300963054f 100644 --- a/src/bin/pkgman/PackageManager.cpp +++ b/src/bin/pkgman/PackageManager.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include "pkgman.h" +#include "JobStateListener.h" using namespace BPackageKit::BPrivate; @@ -332,6 +334,19 @@ PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository) { printf("[%s] Done.\n", repository.Name().String()); + BInstallationLocationInfo info; + if (BPackageRoster().GetInstallationLocationInfo(repository.Location(), info) == B_OK) { + // Offer to delete older state and transaction directories. + BJobStateListener listener; + BContext context(fDecisionProvider, listener); + + const int days = 30; + time_t before = time(NULL) - days * 24 * 60 * 60; + + CleanUpAdminDirectoryRequest request(context, info, before, 10); + request.Process(); + } + if (BPackageRoster().IsRebootNeeded()) printf("A reboot is necessary to complete the installation process.\n"); } diff --git a/src/kits/package/CleanUpAdminDirectoryRequest.cpp b/src/kits/package/CleanUpAdminDirectoryRequest.cpp new file mode 100644 index 0000000000..995bcdd2c0 --- /dev/null +++ b/src/kits/package/CleanUpAdminDirectoryRequest.cpp @@ -0,0 +1,215 @@ +/* + * Copyright 2026, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +namespace BPackageKit { + + +using namespace BPrivate; + + +class CleanUpAdminDirectoryJob : public BJob { + typedef BJob inherited; + +public: + CleanUpAdminDirectoryJob( + const BContext& context, const BString& title, + const BInstallationLocationInfo& location, + time_t cleanupBefore, int32 minStatesToKeep); + virtual ~CleanUpAdminDirectoryJob(); + +protected: + virtual status_t Execute(); + +private: + status_t _GetOldStateDirectories(BStringList& directories); + +private: + const BInstallationLocationInfo& fLocationInfo; + time_t fCleanupBefore; + int32 fMinimumStatesToKeep; +}; + + +CleanUpAdminDirectoryJob::CleanUpAdminDirectoryJob(const BContext& context, + const BString& title, const BInstallationLocationInfo& location, + time_t cleanupBefore, int32 minStatesToKeep) + : + inherited(context, title), + fLocationInfo(location), + fCleanupBefore(cleanupBefore), + fMinimumStatesToKeep(minStatesToKeep) +{ +} + + +CleanUpAdminDirectoryJob::~CleanUpAdminDirectoryJob() +{ +} + + +status_t +CleanUpAdminDirectoryJob::Execute() +{ + BStringList oldStatesAndTransactions; + status_t status = _GetOldStateDirectories(oldStatesAndTransactions); + if (status != B_OK) + return status; + if (oldStatesAndTransactions.IsEmpty()) + return B_OK; + + size_t totalSize = 0; + for (int32 i = 0; i < oldStatesAndTransactions.CountStrings(); i++) { + BDirectory dir(oldStatesAndTransactions.StringAt(i)); + BEntry entry; + while (dir.GetNextEntry(&entry, true) == B_OK) { + off_t entrySize; + if (entry.GetSize(&entrySize) == B_OK) + totalSize += entrySize; + } + } + + static BStringFormat format("{0, plural," + "one{Clean up # old state (%size)?} other{Clean up # old states (%size)?}}"); + + BString question; + format.Format(question, oldStatesAndTransactions.CountStrings()); + + char buffer[128]; + question.ReplaceAll("%size", string_for_size(totalSize, buffer, sizeof(buffer))); + + bool yes = fContext.DecisionProvider().YesNoDecisionNeeded("", question, + "yes", "no", "yes"); + if (!yes) + return B_CANCELED; + + for (int32 i = 0; i < oldStatesAndTransactions.CountStrings(); i++) { + BString directory = oldStatesAndTransactions.StringAt(i); + status_t status = BRemoveEngine().RemoveEntry(BRemoveEngine::Entry( + directory)); + + if (status != B_OK) { + SetErrorString(BString().SetToFormat("Failed to remove %s: %s\n", + directory.String(), strerror(status))); + return status; + } + } + + return B_OK; +} + + +status_t +CleanUpAdminDirectoryJob::_GetOldStateDirectories(BStringList& directories) +{ + BDirectory packages(&fLocationInfo.PackagesDirectoryRef()); + BDirectory administrative(&packages, PACKAGES_DIRECTORY_ADMIN_DIRECTORY); + if (administrative.InitCheck() != B_OK) + return administrative.InitCheck(); + + BStringList states, transactions; + BEntry entry; + int32 skippedStates = 0; + while (administrative.GetNextEntry(&entry) == B_OK) { + if (!entry.IsDirectory()) + continue; + + time_t mtime; + if (entry.GetModificationTime(&mtime) != B_OK) + continue; + + BStringList* list = NULL; + BString name = entry.Name(); + if (name.StartsWith("transaction-")) { + if (mtime >= fCleanupBefore) + continue; + + list = &transactions; + } else if (name.StartsWith("state_")) { + if (mtime >= fCleanupBefore) { + skippedStates++; + continue; + } + if (!fLocationInfo.ActiveStateName().IsEmpty() + && name >= fLocationInfo.ActiveStateName()) { + skippedStates++; + continue; + } + + list = &states; + } else { + continue; + } + + list->Add(BPath(&administrative, name).Path()); + } + + states.Sort(); + while (!states.IsEmpty() && skippedStates < fMinimumStatesToKeep) { + states.Remove(states.CountStrings() - 1); + skippedStates++; + } + + directories.MakeEmpty(); + directories.Add(states); + directories.Add(transactions); + return B_OK; +} + + +CleanUpAdminDirectoryRequest::CleanUpAdminDirectoryRequest(const BContext& context, + const BInstallationLocationInfo& location, + time_t cleanupBefore, int32 minStatesToKeep) + : + inherited(context), + fLocationInfo(location), + fCleanupBefore(cleanupBefore), + fMinimumStatesToKeep(minStatesToKeep) +{ +} + + +CleanUpAdminDirectoryRequest::~CleanUpAdminDirectoryRequest() +{ +} + + +status_t +CleanUpAdminDirectoryRequest::CreateInitialJobs() +{ + status_t result = InitCheck(); + if (result != B_OK) + return B_NO_INIT; + + CleanUpAdminDirectoryJob* cleanUpJob + = new (std::nothrow) CleanUpAdminDirectoryJob(fContext, + "", fLocationInfo, fCleanupBefore, fMinimumStatesToKeep); + if (cleanUpJob == NULL) + return B_NO_MEMORY; + if ((result = QueueJob(cleanUpJob)) != B_OK) { + delete cleanUpJob; + return result; + } + + return B_OK; +} + + +} // namespace BPackageKit diff --git a/src/kits/package/Jamfile b/src/kits/package/Jamfile index a4b497a824..2d27d0fb4d 100644 --- a/src/kits/package/Jamfile +++ b/src/kits/package/Jamfile @@ -77,6 +77,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { AddRepositoryRequest.cpp Attributes.cpp ChecksumAccessors.cpp + CleanUpAdminDirectoryRequest.cpp Context.cpp DaemonClient.cpp DownloadFileRequest.cpp