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:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user