Installer: fix 'clean install' over existing installation.

Whenever the target of an installation is a partition that is not empty, the
Installer prompts the user whether they would like to continue with a 'clean
installation', i.e. an installation that clears out the system folder,
excluding the settings, and cleanly installs the assets in the source's
system folder. At all other locations the source data is merged, meaning that
the source version is copied in place.

The logic that clears out the existing /system/ directory stopped working. This
change moves that logic from the copying process, to where it is run before any
file is copied. The added advantage is that the system folder is now properly
cleaned up, also stray files under the system folder will be removed.

This change does not change the logic of what constitutes a 'clean install'.
There are arguments to be made that it should potentially also drop the
settings files, as well as clean out the user's home folder for stray
add-ons, but that really is different functionality, and at this points I
think the requirements for that are not yet fleshed out.

The change was manually tested.

Fixes #16092

Change-Id: Ia6781c8d2330ba336b3921f9a980b5e31c48a2ec
Reviewed-on: https://review.haiku-os.org/c/haiku/+/3140
Reviewed-by: Andrew Lindesay <[email protected]>
This commit is contained in:
Niels Sascha Reedijk
2020-11-16 09:02:27 +00:00
parent 027d608682
commit dfb36b35b9
4 changed files with 101 additions and 65 deletions
+38 -42
View File
@@ -143,6 +143,30 @@ CopyEngine::Copy(const char* _source, const char* _destination,
}
status_t
CopyEngine::RemoveFolder(BEntry& entry)
{
BDirectory directory(&entry);
status_t ret = directory.InitCheck();
if (ret != B_OK)
return ret;
BEntry subEntry;
while (directory.GetNextEntry(&subEntry) == B_OK) {
if (subEntry.IsDirectory()) {
ret = CopyEngine::RemoveFolder(subEntry);
if (ret != B_OK)
return ret;
} else {
ret = subEntry.Remove();
if (ret != B_OK)
return ret;
}
}
return entry.Remove();
}
status_t
CopyEngine::_CopyData(const BEntry& _source, const BEntry& _destination,
sem_id cancelSemaphore)
@@ -335,16 +359,10 @@ CopyEngine::_Copy(BEntry &source, BEntry &destination,
if (destination.Exists()) {
if (destination.IsDirectory()) {
if (fEntryFilter
&& fEntryFilter->ShouldClobberFolder(source,
relativeSourcePath, sourceInfo)) {
ret = _RemoveFolder(destination);
} else {
// Do not overwrite attributes on folders that exist.
// This should work better when the install target
// already contains a Haiku installation.
copyAttributesToTarget = false;
}
// Do not overwrite attributes on folders that exist.
// This should work better when the install target
// already contains a Haiku installation.
copyAttributesToTarget = false;
} else {
ret = destination.Remove();
}
@@ -354,13 +372,15 @@ CopyEngine::_Copy(BEntry &source, BEntry &destination,
"%s\n", sourcePath.Path(), strerror(ret));
return ret;
}
} else {
ret = create_directory(destPath.Path(), 0777);
if (ret != B_OK && ret != B_FILE_EXISTS) {
fprintf(stderr, "Could not create '%s': %s\n", destPath.Path(),
strerror(ret));
return ret;
}
}
ret = create_directory(destPath.Path(), 0777);
// Make sure the target path exists, it may have been deleted if
// the existing destination was a file instead of a directory.
if (ret != B_OK && ret != B_FILE_EXISTS) {
fprintf(stderr, "Could not create '%s': %s\n", destPath.Path(),
strerror(ret));
return ret;
}
BDirectory destDirectory(&destination);
@@ -381,7 +401,7 @@ CopyEngine::_Copy(BEntry &source, BEntry &destination,
} else {
if (destination.Exists()) {
if (destination.IsDirectory())
ret = _RemoveFolder(destination);
ret = CopyEngine::RemoveFolder(destination);
else
ret = destination.Remove();
if (ret != B_OK) {
@@ -466,30 +486,6 @@ CopyEngine::_Copy(BEntry &source, BEntry &destination,
}
status_t
CopyEngine::_RemoveFolder(BEntry& entry)
{
BDirectory directory(&entry);
status_t ret = directory.InitCheck();
if (ret != B_OK)
return ret;
BEntry subEntry;
while (directory.GetNextEntry(&subEntry) == B_OK) {
if (subEntry.IsDirectory()) {
ret = _RemoveFolder(subEntry);
if (ret != B_OK)
return ret;
} else {
ret = subEntry.Remove();
if (ret != B_OK)
return ret;
}
}
return entry.Remove();
}
const char*
CopyEngine::_RelativeEntryPath(const char* absoluteSourcePath) const
{
+2 -5
View File
@@ -37,6 +37,8 @@ public:
sem_id cancelSemaphore = -1,
bool copyAttributes = true);
static status_t RemoveFolder(BEntry& entry);
private:
status_t _CollectCopyInfo(const char* source,
sem_id cancelSemaphore, off_t& bytesToCopy,
@@ -48,8 +50,6 @@ private:
const BEntry& destination,
sem_id cancelSemaphore = -1);
status_t _RemoveFolder(BEntry& entry);
const char* _RelativeEntryPath(
const char* absoluteSourcePath) const;
@@ -118,9 +118,6 @@ public:
virtual bool ShouldCopyEntry(const BEntry& entry,
const char* path,
const struct stat& statInfo) const = 0;
virtual bool ShouldClobberFolder(const BEntry& entry,
const char* path,
const struct stat& statInfo) const = 0;
};
+59 -18
View File
@@ -137,18 +137,6 @@ public:
return true;
}
virtual bool ShouldClobberFolder(const BEntry& entry, const char* path,
const struct stat& statInfo) const
{
if (S_ISDIR(statInfo.st_mode) && strncmp("system/", path, 7) == 0
&& strcmp("system/settings", path) != 0) {
// Replace everything in "system" besides "settings"
printf("clobbering '%s'.\n", path);
return true;
}
return false;
}
private:
typedef std::set<std::string> StringSet;
@@ -460,12 +448,11 @@ WorkerThread::_PerformInstall(partition_id sourcePartitionID,
if (entries != 0) {
BAlert* alert = new BAlert("", B_TRANSLATE("The target volume is not "
"empty. Are you sure you want to install anyway?\n\nNote: The "
"'system' folder will be a clean copy from the source volume while "
"the existing 'settings' folder is retained. All other folders "
"will be merged, in which files and links that exist on both the "
"source and target volume will be overwritten with the source "
"volume version."),
"empty. If it already contains a Haiku installation, it will be "
"overwritten. This will remove all installed software.\n\n"
"If you want to upgrade your system without removing installed "
"software, see the Haiku user guide for update instructions.\n\n"
"Are you sure you want to continue the installation?"),
B_TRANSLATE("Install anyway"), B_TRANSLATE("Cancel"), 0,
B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetShortcut(1, B_ESCAPE);
@@ -474,6 +461,9 @@ WorkerThread::_PerformInstall(partition_id sourcePartitionID,
// folders at the user's choice.
return _InstallationError(B_CANCELED);
}
err = _PrepareCleanInstall(targetDirectory);
if (err != B_OK)
return _InstallationError(err);
}
// Begin actual installation
@@ -582,6 +572,57 @@ WorkerThread::_PerformInstall(partition_id sourcePartitionID,
}
status_t
WorkerThread::_PrepareCleanInstall(const BPath& targetDirectory) const
{
// When a target volume has files (other than the trash), the /system
// folder will be purged, except for the /system/settings subdirectory.
BPath systemPath(targetDirectory.Path(), "system", true);
status_t ret = systemPath.InitCheck();
if (ret != B_OK)
return ret;
BEntry systemEntry(systemPath.Path());
ret = systemEntry.InitCheck();
if (ret != B_OK)
return ret;
if (!systemEntry.Exists())
// target does not exist, done
return B_OK;
if (!systemEntry.IsDirectory())
// the system entry is a file or a symlink
return systemEntry.Remove();
BDirectory systemDirectory(&systemEntry);
ret = systemDirectory.InitCheck();
if (ret != B_OK)
return ret;
BEntry subEntry;
char fileName[B_FILE_NAME_LENGTH];
while (systemDirectory.GetNextEntry(&subEntry) == B_OK) {
ret = subEntry.GetName(fileName);
if (ret != B_OK)
return ret;
if (subEntry.IsDirectory() && strcmp(fileName, "settings") == 0) {
// Keep the settings folder
continue;
} else if (subEntry.IsDirectory()) {
ret = CopyEngine::RemoveFolder(subEntry);
if (ret != B_OK)
return ret;
} else {
ret = subEntry.Remove();
if (ret != B_OK)
return ret;
}
}
return B_OK;
}
status_t
WorkerThread::_InstallationError(status_t error)
{
+2
View File
@@ -44,6 +44,8 @@ private:
status_t _PerformInstall(partition_id sourcePartitionID,
partition_id targetPartitionID);
status_t _PrepareCleanInstall(
const BPath& targetDirectory) const;
status_t _InstallationError(status_t error);
status_t _MirrorIndices(const BPath& srcDirectory,
const BPath& targetDirectory) const;