diff options
author | grt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-24 17:36:32 +0000 |
---|---|---|
committer | grt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-24 17:36:32 +0000 |
commit | 48ddb167eaedd08cf087fcbfc5e1cecb8112c23e (patch) | |
tree | 31ac686e0a995b6b833517f378e833e53a1f9b4c /chrome/installer/util | |
parent | 06d226e82b3b1f54c22339bdc39ffe97294a3c6a (diff) | |
download | chromium_src-48ddb167eaedd08cf087fcbfc5e1cecb8112c23e.zip chromium_src-48ddb167eaedd08cf087fcbfc5e1cecb8112c23e.tar.gz chromium_src-48ddb167eaedd08cf087fcbfc5e1cecb8112c23e.tar.bz2 |
Fix in-use updates where %TMP% is on a volume other than %USERPROFILE% and/or %SystemRoot%. We now do temp work on the same volume as the eventual install. In the case of system-level installs, this has the nice side-effect of allowing us to move the install into place rather than copy it, which should also speed up system-level installs.
BUG=70368
TEST=Set TMP and TEMP env vars to some volume other than the one holding %USERPROFILE% (for user-level installs) or %SystemRoot% (for system-level installs), then see if in-use updates work.
Review URL: http://codereview.chromium.org/6538091
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75899 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/installer/util')
-rw-r--r-- | chrome/installer/util/copy_tree_work_item.h | 5 | ||||
-rw-r--r-- | chrome/installer/util/delete_after_reboot_helper.cc | 6 | ||||
-rw-r--r-- | chrome/installer/util/self_cleaning_temp_dir.cc | 117 | ||||
-rw-r--r-- | chrome/installer/util/self_cleaning_temp_dir.h | 56 | ||||
-rw-r--r-- | chrome/installer/util/self_cleaning_temp_dir_unittest.cc | 137 | ||||
-rw-r--r-- | chrome/installer/util/util_constants.cc | 1 | ||||
-rw-r--r-- | chrome/installer/util/util_constants.h | 1 | ||||
-rw-r--r-- | chrome/installer/util/work_item_list.h | 2 |
8 files changed, 322 insertions, 3 deletions
diff --git a/chrome/installer/util/copy_tree_work_item.h b/chrome/installer/util/copy_tree_work_item.h index 1e248ce..a1923ce 100644 --- a/chrome/installer/util/copy_tree_work_item.h +++ b/chrome/installer/util/copy_tree_work_item.h @@ -19,6 +19,11 @@ // to the temporary directory passed in, and then copies the source hierarchy // to the destination location. During rollback the original destination // hierarchy is moved back. +// NOTE: It is a best practice to ensure that the temporary directory is on the +// same volume as the destination path. If this is not the case, the existing +// destination path is not moved, but rather copied, to the destination path. +// This will result in in-use files being left behind, as well as potentially +// losing ACLs or other metadata in the case of a rollback. class CopyTreeWorkItem : public WorkItem { public: virtual ~CopyTreeWorkItem(); diff --git a/chrome/installer/util/delete_after_reboot_helper.cc b/chrome/installer/util/delete_after_reboot_helper.cc index 4a50df5..6583250 100644 --- a/chrome/installer/util/delete_after_reboot_helper.cc +++ b/chrome/installer/util/delete_after_reboot_helper.cc @@ -79,7 +79,7 @@ bool ScheduleFileSystemEntityForDeletion(const wchar_t* path) { HANDLE file = ::CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (file != INVALID_HANDLE_VALUE) { - PLOG(INFO) << " file not in use: " << path; + LOG(INFO) << " file not in use: " << path; ::CloseHandle(file); } else { PLOG(INFO) << " file in use (or not found?): " << path; @@ -103,7 +103,7 @@ bool ScheduleDirectoryForDeletion(const wchar_t* dir_name) { if (::GetLastError() == ERROR_FILE_NOT_FOUND) { return true; // Ok if directory is missing } else { - LOG(ERROR) << "Could not GetFileAttributes for " << dir_name; + PLOG(ERROR) << "Could not GetFileAttributes for " << dir_name; return false; } } @@ -384,7 +384,7 @@ bool RemoveFromMovesPendingReboot(const wchar_t* directory) { } std::vector<char> buffer; StringArrayToMultiSZBytes(strings_to_keep, &buffer); - DCHECK(buffer.size() > 0); + DCHECK_GT(buffer.size(), 0U); if (buffer.empty()) return false; return (session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0], diff --git a/chrome/installer/util/self_cleaning_temp_dir.cc b/chrome/installer/util/self_cleaning_temp_dir.cc new file mode 100644 index 0000000..a95a722 --- /dev/null +++ b/chrome/installer/util/self_cleaning_temp_dir.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 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/self_cleaning_temp_dir.h" + +#include <windows.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "chrome/installer/util/delete_after_reboot_helper.h" + +namespace installer { + +// Populates |base_dir| with the topmost directory in the hierarchy of +// |temp_parent_dir| that does not exist. If |temp_parent_dir| exists, +// |base_dir| is cleared. +// static +void SelfCleaningTempDir::GetTopDirToCreate(const FilePath& temp_parent_dir, + FilePath* base_dir) { + DCHECK(base_dir); + + if (file_util::PathExists(temp_parent_dir)) { + // Empty base_dir means that we didn't create any extra directories. + base_dir->clear(); + } else { + FilePath parent_dir(temp_parent_dir); + do { + *base_dir = parent_dir; + parent_dir = parent_dir.DirName(); + } while (parent_dir != *base_dir && !file_util::PathExists(parent_dir)); + LOG_IF(WARNING, !file_util::DirectoryExists(parent_dir)) + << "A non-directory is at the base of the path leading to a desired " + "temp directory location: " << parent_dir.value(); + } +} + +SelfCleaningTempDir::SelfCleaningTempDir() { +} + +SelfCleaningTempDir::~SelfCleaningTempDir() { + if (!path().empty() && !Delete()) + LOG(WARNING) << "Failed to clean temp dir in dtor " << path().value(); +} + +bool SelfCleaningTempDir::Initialize(const FilePath& parent_dir, + const StringType& temp_name) { + DCHECK(parent_dir.IsAbsolute()); + DCHECK(!temp_name.empty()); + + if (!path().empty()) { + LOG(DFATAL) << "Attempting to re-initialize a SelfSelfCleaningTempDir."; + return false; + } + + FilePath temp_dir(parent_dir.Append(temp_name)); + FilePath base_dir; + GetTopDirToCreate(parent_dir, &base_dir); + + if (file_util::CreateDirectory(temp_dir)) { + base_dir_ = base_dir; + temp_dir_ = temp_dir; + return true; + } + + return false; +} + +bool SelfCleaningTempDir::Delete() { + if (path().empty()) { + LOG(DFATAL) << "Attempting to Delete an uninitialized SelfCleaningTempDir."; + return false; + } + + FilePath next_dir(path().DirName()); + bool schedule_deletes = false; + + // First try to recursively delete the leaf directory managed by our + // ScopedTempDir. + if (!file_util::Delete(path(), true)) { + // That failed, so schedule the temp dir and its contents for deletion after + // reboot. + LOG(WARNING) << "Failed to delete temporary directory " << path().value() + << ". Scheduling for deletion at reboot."; + schedule_deletes = true; + if (!ScheduleDirectoryForDeletion(path().value().c_str())) + return false; // Entirely unexpected failure (Schedule logs the reason). + } + + // Now delete or schedule all empty directories up to and including our + // base_dir_. Any that can't be deleted are scheduled for deletion at reboot. + // This is safe since they'll only be deleted in that case if they're empty. + if (!base_dir_.empty()) { + do { + if (!schedule_deletes && !RemoveDirectory(next_dir.value().c_str())) { + PLOG_IF(WARNING, GetLastError() != ERROR_DIR_NOT_EMPTY) + << "Error removing directory " << next_dir.value().c_str(); + schedule_deletes = true; + } + if (schedule_deletes) { + // Ignore the return code. If we fail to schedule, go ahead and add the + // other parent directories anyway. + ScheduleFileSystemEntityForDeletion(next_dir.value().c_str()); + } + if (next_dir == base_dir_) + break; // We just processed the topmost directory we created. + next_dir = next_dir.DirName(); + } while (true); + } + + base_dir_.clear(); + temp_dir_.clear(); + + return true; +} + +} // namespace installer diff --git a/chrome/installer/util/self_cleaning_temp_dir.h b/chrome/installer/util/self_cleaning_temp_dir.h new file mode 100644 index 0000000..d9e3ad5 --- /dev/null +++ b/chrome/installer/util/self_cleaning_temp_dir.h @@ -0,0 +1,56 @@ +// Copyright (c) 2011 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_SELF_CLEANING_TEMP_DIR_H_ +#define CHROME_INSTALLER_UTIL_SELF_CLEANING_TEMP_DIR_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/gtest_prod_util.h" + +namespace installer { + +// A helper class for managing a temporary directory. In relation to +// ScopedTempDir, this class additionally cleans up all non-empty parent +// directories of the temporary directory that are created by an instance. +class SelfCleaningTempDir { + public: + typedef FilePath::StringType StringType; + + SelfCleaningTempDir(); + + // Performs a Delete(). + ~SelfCleaningTempDir(); + + // Creates a temporary directory named |temp_name| under |parent_dir|, + // creating intermediate directories as needed. + bool Initialize(const FilePath& parent_dir, const StringType& temp_name); + + // Returns the temporary directory created in Initialize(). + const FilePath& path() const { return temp_dir_; } + + // Deletes the temporary directory created in Initialize() and all of its + // contents, as well as all empty intermediate directories. Any of these that + // cannot be deleted immediately are scheduled for deletion upon reboot. + bool Delete(); + + private: + static void GetTopDirToCreate(const FilePath& temp_parent_dir, + FilePath* base_dir); + + // The topmost directory created. + FilePath base_dir_; + + // The temporary directory. + FilePath temp_dir_; + + FRIEND_TEST_ALL_PREFIXES(SelfCleaningTempDirTest, TopLevel); + FRIEND_TEST_ALL_PREFIXES(SelfCleaningTempDirTest, TopLevelPlusOne); + DISALLOW_COPY_AND_ASSIGN(SelfCleaningTempDir); +}; + +} // namespace installer + +#endif // CHROME_INSTALLER_UTIL_SELF_CLEANING_TEMP_DIR_H_ diff --git a/chrome/installer/util/self_cleaning_temp_dir_unittest.cc b/chrome/installer/util/self_cleaning_temp_dir_unittest.cc new file mode 100644 index 0000000..5980575 --- /dev/null +++ b/chrome/installer/util/self_cleaning_temp_dir_unittest.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2011 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 <windows.h> +#include <wincrypt.h> + +#include "base/file_util.h" +#include "base/scoped_temp_dir.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/installer/util/self_cleaning_temp_dir.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Returns a string of 8 characters consisting of the letter 'R' followed by +// seven random hex digits. +std::wstring GetRandomFilename() { + uint8 data[4]; + HCRYPTPROV crypt_ctx = NULL; + + // Get four bytes of randomness. Use CAPI rather than the CRT since I've + // seen the latter trivially repeat. + EXPECT_NE(FALSE, CryptAcquireContext(&crypt_ctx, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)); + EXPECT_NE(FALSE, CryptGenRandom(crypt_ctx, arraysize(data), &data[0])); + EXPECT_NE(FALSE, CryptReleaseContext(crypt_ctx, 0)); + + // Hexify the value. + std::string result(base::HexEncode(&data[0], arraysize(data))); + EXPECT_EQ(8, result.size()); + + // Replace the first digit with the letter 'R' (for "random", get it?). + result[0] = 'R'; + + return ASCIIToWide(result); +} + +} // namespace + +namespace installer { + +class SelfCleaningTempDirTest : public testing::Test { +}; + +// Test the implementation of GetTopDirToCreate when given the root of a +// volume. +TEST_F(SelfCleaningTempDirTest, TopLevel) { + FilePath base_dir; + SelfCleaningTempDir::GetTopDirToCreate(FilePath(L"C:\\"), &base_dir); + EXPECT_TRUE(base_dir.empty()); +} + +// Test the implementation of GetTopDirToCreate when given a non-existant dir +// under the root of a volume. +TEST_F(SelfCleaningTempDirTest, TopLevelPlusOne) { + FilePath base_dir; + FilePath parent_dir(L"C:\\"); + parent_dir = parent_dir.Append(GetRandomFilename()); + SelfCleaningTempDir::GetTopDirToCreate(parent_dir, &base_dir); + EXPECT_EQ(parent_dir, base_dir); +} + +// Test that all intermediate dirs are cleaned up if they're empty when +// Delete() is called. +TEST_F(SelfCleaningTempDirTest, RemoveUnusedOnDelete) { + // Make a directory in which we'll work. + ScopedTempDir work_dir; + EXPECT_TRUE(work_dir.CreateUniqueTempDir()); + + // Make up some path under the temp dir. + FilePath parent_temp_dir(work_dir.path().Append(L"One").Append(L"Two")); + SelfCleaningTempDir temp_dir; + EXPECT_TRUE(temp_dir.Initialize(parent_temp_dir, L"Three")); + EXPECT_EQ(parent_temp_dir.Append(L"Three"), temp_dir.path()); + EXPECT_TRUE(file_util::DirectoryExists(temp_dir.path())); + EXPECT_TRUE(temp_dir.Delete()); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.Append(L"Three"))); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir)); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.DirName())); + EXPECT_TRUE(file_util::DirectoryExists(parent_temp_dir.DirName().DirName())); + EXPECT_TRUE(work_dir.Delete()); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.DirName().DirName())); +} + +// Test that all intermediate dirs are cleaned up if they're empty when the +// destructor is called. +TEST_F(SelfCleaningTempDirTest, RemoveUnusedOnDestroy) { + // Make a directory in which we'll work. + ScopedTempDir work_dir; + EXPECT_TRUE(work_dir.CreateUniqueTempDir()); + + // Make up some path under the temp dir. + FilePath parent_temp_dir(work_dir.path().Append(L"One").Append(L"Two")); + { + SelfCleaningTempDir temp_dir; + EXPECT_TRUE(temp_dir.Initialize(parent_temp_dir, L"Three")); + EXPECT_EQ(parent_temp_dir.Append(L"Three"), temp_dir.path()); + EXPECT_TRUE(file_util::DirectoryExists(temp_dir.path())); + } + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.Append(L"Three"))); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir)); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.DirName())); + EXPECT_TRUE(file_util::DirectoryExists(parent_temp_dir.DirName().DirName())); + EXPECT_TRUE(work_dir.Delete()); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.DirName().DirName())); +} + +// Test that intermediate dirs are left behind if they're not empty when the +// destructor is called. +TEST_F(SelfCleaningTempDirTest, LeaveUsedOnDestroy) { + static const char kHiHon[] = "hi, hon"; + + // Make a directory in which we'll work. + ScopedTempDir work_dir; + EXPECT_TRUE(work_dir.CreateUniqueTempDir()); + + // Make up some path under the temp dir. + FilePath parent_temp_dir(work_dir.path().Append(L"One").Append(L"Two")); + { + SelfCleaningTempDir temp_dir; + EXPECT_TRUE(temp_dir.Initialize(parent_temp_dir, L"Three")); + EXPECT_EQ(parent_temp_dir.Append(L"Three"), temp_dir.path()); + EXPECT_TRUE(file_util::DirectoryExists(temp_dir.path())); + // Drop a file somewhere. + EXPECT_EQ(arraysize(kHiHon) - 1, + file_util::WriteFile(parent_temp_dir.Append(GetRandomFilename()), + kHiHon, arraysize(kHiHon) - 1)); + } + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.Append(L"Three"))); + EXPECT_TRUE(file_util::DirectoryExists(parent_temp_dir)); + EXPECT_TRUE(work_dir.Delete()); + EXPECT_FALSE(file_util::DirectoryExists(parent_temp_dir.DirName().DirName())); +} + +} // namespace installer diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc index d815498..6c4bbd7 100644 --- a/chrome/installer/util/util_constants.cc +++ b/chrome/installer/util/util_constants.cc @@ -166,6 +166,7 @@ const wchar_t kGoogleChromeInstallSubDir1[] = L"Google"; const wchar_t kGoogleChromeInstallSubDir2[] = L"Chrome"; const wchar_t kInstallBinaryDir[] = L"Application"; const wchar_t kInstallerDir[] = L"Installer"; +const wchar_t kInstallTempDir[] = L"Temp"; const wchar_t kInstallUserDataDir[] = L"User Data"; const wchar_t kNaClExe[] = L"nacl64.exe"; const wchar_t kSetupExe[] = L"setup.exe"; diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h index 997f87b..a44275e 100644 --- a/chrome/installer/util/util_constants.h +++ b/chrome/installer/util/util_constants.h @@ -139,6 +139,7 @@ extern const wchar_t kGoogleChromeInstallSubDir1[]; extern const wchar_t kGoogleChromeInstallSubDir2[]; extern const wchar_t kInstallBinaryDir[]; extern const wchar_t kInstallerDir[]; +extern const wchar_t kInstallTempDir[]; extern const wchar_t kInstallUserDataDir[]; extern const wchar_t kNaClExe[]; extern const wchar_t kSetupExe[]; diff --git a/chrome/installer/util/work_item_list.h b/chrome/installer/util/work_item_list.h index fc826ed..05aeaf2 100644 --- a/chrome/installer/util/work_item_list.h +++ b/chrome/installer/util/work_item_list.h @@ -38,6 +38,8 @@ class WorkItemList : public WorkItem { virtual void AddWorkItem(WorkItem* work_item); // Add a CopyTreeWorkItem to the list of work items. + // See the NOTE in the documentation for the CopyTreeWorkItem class for + // special considerations regarding |temp_dir|. virtual WorkItem* AddCopyTreeWorkItem( const std::wstring& source_path, const std::wstring& dest_path, |