summaryrefslogtreecommitdiffstats
path: root/chrome/installer/util
diff options
context:
space:
mode:
authorgrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-24 17:36:32 +0000
committergrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-24 17:36:32 +0000
commit48ddb167eaedd08cf087fcbfc5e1cecb8112c23e (patch)
tree31ac686e0a995b6b833517f378e833e53a1f9b4c /chrome/installer/util
parent06d226e82b3b1f54c22339bdc39ffe97294a3c6a (diff)
downloadchromium_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.h5
-rw-r--r--chrome/installer/util/delete_after_reboot_helper.cc6
-rw-r--r--chrome/installer/util/self_cleaning_temp_dir.cc117
-rw-r--r--chrome/installer/util/self_cleaning_temp_dir.h56
-rw-r--r--chrome/installer/util/self_cleaning_temp_dir_unittest.cc137
-rw-r--r--chrome/installer/util/util_constants.cc1
-rw-r--r--chrome/installer/util/util_constants.h1
-rw-r--r--chrome/installer/util/work_item_list.h2
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,