Commit 32c0f60f authored by fdoray's avatar fdoray Committed by Commit bot

Delete old files after an update.

This is a variation of r378802 which was reverted because of
a Dr Memory error (solved by using GenerateAlternatePEFileVersion
instead of GenerateSpecificPEFileVersion). r378802 also caused
problems with shortcuts crbug.com/592040 (solved by not letting
the deletion of old files cause a rollback).

With this CL, the installer deletes all files that belong to old
versions of Chrome that are not in use after an update.

Eventually, the DeleteOldVersions() will be invoked again to delete
old versions that were in use at time of the update.

BUG=451546
TEST=
-- Not-in-use --
1. Run mini_installer.exe --chrome --multi-install --verbose-logging --do-not-launch-chrome
- The install directory should contain chrome.exe and a matching version directory.
- There should be no other executable or version directory in the install directory.
2. Run next_version_mini_installer.exe --multi-install --verbose-logging --do-not-launch-chrome
- The install directory should contain chrome.exe and a matching version directory. The version should be higher than at the previous step.
- There should be no other executable or version directory in the install directory.

-- In-use --
1. Run mini_installer.exe --chrome --multi-install --verbose-logging --do-not-launch-chrome
- The install directory should contain chrome.exe and a matching version directory.
- There should be no other executable or version directory in the install directory.
2. Launch Chrome.
3. Run next_version_mini_installer.exe --multi-install --verbose-logging --do-not-launch-chrome.
- The install directory should contain chrome.exe and a matching version directory + new_chrome.exe and a matching version directory.
- There should be no other executable or version directory in the install directory.

Review-Url: https://codereview.chromium.org/2273113002
Cr-Commit-Position: refs/heads/master@{#414842}
parent 57f4baf8
......@@ -32,6 +32,7 @@
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/create_reg_key_work_item.h"
#include "chrome/installer/util/delete_after_reboot_helper.h"
#include "chrome/installer/util/delete_old_versions.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
......@@ -680,11 +681,9 @@ InstallStatus InstallOrUpdateProduct(
}
installer_state.SetStage(REMOVING_OLD_VERSIONS);
installer_state.RemoveOldVersionDirectories(
new_version,
existing_version.get(),
install_temp_path);
// TODO(fdoray): Launch a cleanup process when this fails during a not-in-
// use update. crbug.com/451546
DeleteOldVersions(installer_state.target_path());
}
return result;
......
......@@ -159,6 +159,8 @@ static_library("with_no_strings") {
"create_dir_work_item.h",
"create_reg_key_work_item.cc",
"create_reg_key_work_item.h",
"delete_old_versions.cc",
"delete_old_versions.h",
"delete_reg_key_work_item.cc",
"delete_reg_key_work_item.h",
"delete_reg_value_work_item.cc",
......@@ -292,6 +294,7 @@ if (is_win) {
"create_dir_work_item_unittest.cc",
"create_reg_key_work_item_unittest.cc",
"delete_after_reboot_helper_unittest.cc",
"delete_old_versions_unittest.cc",
"delete_reg_key_work_item_unittest.cc",
"delete_reg_value_work_item_unittest.cc",
"delete_tree_work_item_unittest.cc",
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/installer/util/delete_old_versions.h"
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "base/file_version_info.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/version.h"
#include "chrome/installer/util/util_constants.h"
namespace installer {
namespace {
using PathVector = std::vector<base::FilePath>;
using DirectorySet = std::set<base::FilePath>;
using ExecutableMap = std::map<base::FilePath, PathVector>;
// Returns the name of the version directory for executable |exe_path|.
base::FilePath GetExecutableVersionDirName(const base::FilePath& exe_path) {
std::unique_ptr<FileVersionInfo> file_version_info(
FileVersionInfo::CreateFileVersionInfo(exe_path));
if (!file_version_info.get())
return base::FilePath();
return base::FilePath(file_version_info->file_version());
}
// Returns the names of the old version directories found in |install_dir|. The
// directories named after the version of chrome.exe or new_chrome.exe are
// excluded.
DirectorySet GetOldVersionDirectories(const base::FilePath& install_dir) {
const base::FilePath new_chrome_exe_version_dir_name =
GetExecutableVersionDirName(install_dir.Append(kChromeNewExe));
const base::FilePath chrome_exe_version_dir_name =
GetExecutableVersionDirName(install_dir.Append(kChromeExe));
DirectorySet directories;
base::FileEnumerator enum_directories(install_dir, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath directory_path = enum_directories.Next();
!directory_path.empty(); directory_path = enum_directories.Next()) {
const base::FilePath directory_name = directory_path.BaseName();
const base::Version version(directory_name.AsUTF8Unsafe());
const size_t kNumChromeVersionComponents = 4;
if (version.IsValid() &&
version.components().size() == kNumChromeVersionComponents &&
directory_name != new_chrome_exe_version_dir_name &&
directory_name != chrome_exe_version_dir_name) {
directories.insert(directory_name);
}
}
return directories;
}
// Returns a map where the keys are version directory names and values are paths
// of old_chrome*.exe executables found in |install_dir|.
ExecutableMap GetOldExecutables(const base::FilePath& install_dir) {
ExecutableMap executables;
base::FileEnumerator enum_executables(install_dir, false,
base::FileEnumerator::FILES,
FILE_PATH_LITERAL("old_chrome*.exe"));
for (base::FilePath exe_path = enum_executables.Next(); !exe_path.empty();
exe_path = enum_executables.Next()) {
executables[GetExecutableVersionDirName(exe_path)].push_back(exe_path);
}
return executables;
}
// Deletes directories that are in |directories| and don't have a matching
// executable in |executables|. Returns false if any such directories could not
// be deleted.
bool DeleteDirectoriesWithoutMatchingExecutable(
const DirectorySet& directories,
const ExecutableMap& executables,
const base::FilePath& install_dir) {
bool success = true;
for (const base::FilePath& directory_name : directories) {
// Delete the directory if it doesn't have a matching executable.
if (!base::ContainsKey(executables, directory_name)) {
const base::FilePath directory_path = install_dir.Append(directory_name);
LOG(WARNING) << "Attempting to delete stray directory "
<< directory_path.value();
if (!base::DeleteFile(directory_path, true)) {
PLOG(ERROR) << "Failed to delete stray directory "
<< directory_path.value();
success = false;
}
}
}
return success;
}
// Deletes executables that are in |executables| and don't have a matching
// directory in |directories|. Returns false if any such files could not be
// deleted.
bool DeleteExecutablesWithoutMatchingDirectory(
const DirectorySet& directories,
const ExecutableMap& executables) {
bool success = true;
for (const auto& version_and_executables : executables) {
const auto& version_dir_name = version_and_executables.first;
const auto& executables_for_version = version_and_executables.second;
// Don't delete the executables if they have a matching directory.
if (base::ContainsValue(directories, version_dir_name))
continue;
// Delete executables for version |version_dir_name|.
for (const auto& executable_path : executables_for_version) {
const base::FilePath executable_name = executable_path.BaseName();
LOG(WARNING) << "Attempting to delete stray executable "
<< executable_path.value();
if (!base::DeleteFile(executable_path, false)) {
PLOG(ERROR) << "Failed to delete stray executable "
<< executable_path.value();
success = false;
}
}
}
return success;
}
// Opens |path| with options that prevent the file from being read or written
// via another handle. As long as the returned object is alive, it is guaranteed
// that |path| isn't in use. It can however be deleted.
base::File GetFileLock(const base::FilePath& path) {
return base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ |
base::File::FLAG_EXCLUSIVE_WRITE |
base::File::FLAG_SHARE_DELETE);
}
// Deletes |version_directory| and all executables in |version_executables| if
// no .exe or .dll file for the version is in use. Returns false if any file
// or directory for the version could not be deleted.
bool DeleteVersion(const base::FilePath& version_directory,
const PathVector& version_executables) {
std::vector<base::File> locks;
PathVector locked_file_paths;
// Lock .exe/.dll files in |version_directory|.
base::FileEnumerator enum_version_directory(version_directory, true,
base::FileEnumerator::FILES);
for (base::FilePath path = enum_version_directory.Next(); !path.empty();
path = enum_version_directory.Next()) {
if (!path.MatchesExtension(FILE_PATH_LITERAL(".exe")) &&
!path.MatchesExtension(FILE_PATH_LITERAL(".dll"))) {
continue;
}
locks.push_back(GetFileLock(path));
if (!locks.back().IsValid()) {
LOG(WARNING) << "Failed to delete old version "
<< version_directory.value() << " because " << path.value()
<< " is in use.";
return false;
}
locked_file_paths.push_back(path);
}
// Lock executables in |version_executables|.
for (const base::FilePath& executable_path : version_executables) {
locks.push_back(GetFileLock(executable_path));
if (!locks.back().IsValid()) {
LOG(WARNING) << "Failed to delete old version "
<< version_directory.value() << " because "
<< executable_path.value() << " is in use.";
return false;
}
locked_file_paths.push_back(executable_path);
}
bool success = true;
// Delete locked files. The files won't actually be deleted until the locks
// are released.
for (const base::FilePath& locked_file_path : locked_file_paths) {
if (!base::DeleteFile(locked_file_path, false)) {
PLOG(ERROR) << "Failed to delete locked file "
<< locked_file_path.value();
success = false;
}
}
// Release the locks, causing the locked files to actually be deleted. The
// version directory can't be deleted before this is done.
locks.clear();
// Delete the version directory.
if (!base::DeleteFile(version_directory, true)) {
PLOG(ERROR) << "Failed to delete version directory "
<< version_directory.value();
success = false;
}
return success;
}
// For each executable in |executables| that has a matching directory in
// |directories|, tries to delete the executable and the matching directory. No
// deletion occurs for a given version if a .exe or .dll file for that version
// is in use. Returns false if any directory/executables pair could not be
// deleted.
bool DeleteMatchingExecutablesAndDirectories(
const DirectorySet& directories,
const ExecutableMap& executables,
const base::FilePath& install_dir) {
bool success = true;
for (const auto& directory_name : directories) {
// Don't delete the version unless the directory has at least one matching
// executable.
auto version_executables_it = executables.find(directory_name);
if (version_executables_it == executables.end())
continue;
// Try to delete all files for the version.
success &= DeleteVersion(install_dir.Append(directory_name),
version_executables_it->second);
}
return success;
}
} // namespace
bool DeleteOldVersions(const base::FilePath& install_dir) {
const DirectorySet old_directories = GetOldVersionDirectories(install_dir);
const ExecutableMap old_executables = GetOldExecutables(install_dir);
bool success = true;
success &= DeleteDirectoriesWithoutMatchingExecutable(
old_directories, old_executables, install_dir);
success &= DeleteExecutablesWithoutMatchingDirectory(old_directories,
old_executables);
success &= DeleteMatchingExecutablesAndDirectories(
old_directories, old_executables, install_dir);
return success;
}
} // namespace installer
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_INSTALLER_UTIL_DELETE_OLD_VERSIONS_H_
#define CHROME_INSTALLER_UTIL_DELETE_OLD_VERSIONS_H_
namespace base {
class FilePath;
}
namespace installer {
// Deletes files that belong to old versions of Chrome. chrome.exe,
// new_chrome.exe and their associated version directories are never deleted.
// Also, no file is deleted for a given version if a .exe or .dll file for that
// version is in use. Returns true if no files that belong to an old version of
// Chrome remain.
bool DeleteOldVersions(const base::FilePath& install_dir);
} // namespace installer
#endif // CHROME_INSTALLER_UTIL_DELETE_OLD_VERSIONS_H_
This diff is collapsed.
......@@ -12,12 +12,9 @@
#include <utility>
#include "base/command_line.h"
#include "base/file_version_info.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
......@@ -514,69 +511,6 @@ bool InstallerState::AnyExistsAndIsInUse(const InstallationState& machine_state,
return false;
}
void InstallerState::GetExistingExeVersions(
std::set<std::string>* existing_versions) const {
static const wchar_t* const kChromeFilenames[] = {
installer::kChromeExe,
installer::kChromeNewExe,
installer::kChromeOldExe,
};
for (size_t i = 0; i < arraysize(kChromeFilenames); ++i) {
base::FilePath chrome_exe(target_path().Append(kChromeFilenames[i]));
std::unique_ptr<FileVersionInfo> file_version_info(
FileVersionInfo::CreateFileVersionInfo(chrome_exe));
if (file_version_info) {
base::string16 version_string = file_version_info->file_version();
if (!version_string.empty() && base::IsStringASCII(version_string))
existing_versions->insert(base::UTF16ToASCII(version_string));
}
}
}
void InstallerState::RemoveOldVersionDirectories(
const base::Version& new_version,
base::Version* existing_version,
const base::FilePath& temp_path) const {
base::Version version;
std::unique_ptr<WorkItem> item;
std::set<std::string> existing_version_strings;
existing_version_strings.insert(new_version.GetString());
if (existing_version)
existing_version_strings.insert(existing_version->GetString());
// Make sure not to delete any version dir that is "referenced" by an existing
// Chrome executable.
GetExistingExeVersions(&existing_version_strings);
// Try to delete all directories that are not in the set we care to keep.
base::FileEnumerator version_enum(target_path(), false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath next_version = version_enum.Next(); !next_version.empty();
next_version = version_enum.Next()) {
base::FilePath dir_name(next_version.BaseName());
version = base::Version(base::UTF16ToASCII(dir_name.value()));
// Delete the version folder if it is less than the new version and not
// equal to the old version (if we have an old version).
if (version.IsValid() &&
existing_version_strings.count(version.GetString()) == 0) {
// Note: temporarily log old version deletion at ERROR level to make it
// more likely we see this in the installer log.
LOG(ERROR) << "Deleting old version directory: " << next_version.value();
// Attempt to recursively delete the old version dir.
bool delete_succeeded = base::DeleteFile(next_version, true);
// Note: temporarily log old version deletion at ERROR level to make it
// more likely we see this in the installer log.
LOG_IF(ERROR, !delete_succeeded)
<< "Failed to delete old version directory: " << next_version.value();
}
}
}
void InstallerState::AddComDllList(
std::vector<base::FilePath>* com_dll_list) const {
for (auto* product : products_)
......
......@@ -8,7 +8,6 @@
#include <stdint.h>
#include <memory>
#include <set>
#include <string>
#include <vector>
......@@ -185,13 +184,6 @@ class InstallerState {
// (for example <target_path>\Google\Chrome\Application\<Version>\Installer)
base::FilePath GetInstallerDirectory(const base::Version& version) const;
// Try to delete all directories under |temp_path| whose versions are less
// than |new_version| and not equal to |existing_version|. |existing_version|
// may be NULL.
void RemoveOldVersionDirectories(const base::Version& new_version,
base::Version* existing_version,
const base::FilePath& temp_path) const;
// Adds to |com_dll_list| the list of COM DLLs that are to be registered
// and/or unregistered. The list may be empty.
void AddComDllList(std::vector<base::FilePath>* com_dll_list) const;
......@@ -251,11 +243,6 @@ class InstallerState {
bool IsMultiInstallUpdate(const MasterPreferences& prefs,
const InstallationState& machine_state);
// Enumerates all files named one of
// [chrome.exe, old_chrome.exe, new_chrome.exe] in target_path_ and
// returns their version numbers in a set.
void GetExistingExeVersions(std::set<std::string>* existing_versions) const;
// Sets this object's level and updates the root_key_ accordingly.
void set_level(Level level);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment