Commit 67667a8e authored by Greg Thompson's avatar Greg Thompson Committed by Commit Bot

Stop leaking files extracted by mini_installer.

With this CL, mini_installer now holds the files it creates open with
FLAG_DELETE_ON_CLOSE so that they are automatically deleted by the
filesystem when they are no longer needed.

The most risky operation is the multi-step process to drop write
permission on the files so that they can be opened for consumption by
parties that do not allow writers. New process exits codes have been
introduced to track whether or not this operation fails in practice.

BUG=516207

Change-Id: Ic5e25692cf3dca0fcc7cd01faf5759648f5c6890
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2307250Reviewed-by: default avatarWill Harris <wfh@chromium.org>
Commit-Queue: Greg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790735}
parent 03ae2f46
......@@ -76,6 +76,7 @@ source_set("unit_tests") {
"mini_file_test.cc",
"mini_installer_unittest.cc",
"mini_string_test.cc",
"pe_resource_test.cc",
]
public_deps = [ ":lib" ]
......
......@@ -22,7 +22,7 @@ struct ExpandContext {
const wchar_t* const dest_path;
// The destination file; valid once the destination is created.
mini_installer::MiniFile dest_file;
mini_installer::MiniFile& dest_file;
// Set to true if the file was extracted to |dest_path|. Note that |dest_file|
// may be valid even in case of failure.
......@@ -247,7 +247,7 @@ bool InitializeFdi() {
namespace mini_installer {
bool Expand(const wchar_t* source, const wchar_t* destination) {
bool Expand(const wchar_t* source, const wchar_t* destination, MiniFile& file) {
if (!InitializeFdi())
return false;
......@@ -274,7 +274,7 @@ bool Expand(const wchar_t* source, const wchar_t* destination) {
if (!fdi)
return false;
ExpandContext context = {destination, {}, /*succeeded=*/false};
ExpandContext context = {destination, file, /*succeeded=*/false};
g_FDICopy(fdi, source_name_utf8, source_path_utf8, 0, &Notify, nullptr,
&context);
g_FDIDestroy(fdi);
......@@ -282,8 +282,7 @@ bool Expand(const wchar_t* source, const wchar_t* destination) {
return true;
// Delete the output file if it was created.
if (context.dest_file.IsValid())
context.dest_file.DeleteOnClose();
file.Close();
return false;
}
......
......@@ -7,11 +7,14 @@
namespace mini_installer {
// Same as the tool, expand.exe. Decompresses a file that was compressed
// using Microsoft's MSCF compression algorithm.
// |source| is the full path of the file to decompress and |destination|
// is the full path of the target file.
bool Expand(const wchar_t* source, const wchar_t* destination);
class MiniFile;
// Expands the first file in |source| to the file |destination| using
// Microsoft's MSCF compression algorithm (a la expand.exe). Returns true on
// success, in which case |file| holds an open handle to the destination file.
// |file| will be opened with exclusive write access and shared read and delete
// access, and will be marked as delete-on-close.
bool Expand(const wchar_t* source, const wchar_t* destination, MiniFile& file);
} // namespace mini_installer
......
......@@ -5,9 +5,11 @@
#include <windows.h>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "chrome/installer/mini_installer/decompress.h"
#include "chrome/installer/mini_installer/mini_file.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(MiniDecompressTest, ExpandTest) {
......@@ -27,11 +29,21 @@ TEST(MiniDecompressTest, ExpandTest) {
temp_dir.GetPath().Append(FILE_PATH_LITERAL("setup.exe")));
// Decompress our test file.
EXPECT_TRUE(mini_installer::Expand(source_path.value().c_str(),
dest_path.value().c_str()));
mini_installer::MiniFile file(mini_installer::MiniFile::DeleteOnClose::kYes);
ASSERT_TRUE(mini_installer::Expand(source_path.value().c_str(),
dest_path.value().c_str(), file));
ASSERT_TRUE(file.IsValid());
ASSERT_PRED1(base::PathExists, dest_path);
// Drop write permission so that the call below can open the file.
ASSERT_TRUE(file.DropWritePermission());
// Check if the expanded file is a valid executable.
DWORD type = static_cast<DWORD>(-1);
EXPECT_TRUE(GetBinaryType(dest_path.value().c_str(), &type));
EXPECT_EQ(static_cast<DWORD>(SCS_32BIT_BINARY), type);
// Closing the handle should delete the file.
file.Close();
EXPECT_FALSE(base::PathExists(dest_path));
}
......@@ -42,6 +42,11 @@ enum ExitCode {
RUN_SETUP_FAILED_FILE_NOT_FOUND = 122, // ERROR_FILE_NOT_FOUND.
RUN_SETUP_FAILED_PATH_NOT_FOUND = 123, // ERROR_PATH_NOT_FOUND.
RUN_SETUP_FAILED_COULD_NOT_CREATE_PROCESS = 124, // All other errors.
UNABLE_TO_OPEN_PATCHED_SETUP = 125,
DROP_WRITE_ON_EXTRACTED_ARCHIVE_FAILED = 126,
DROP_WRITE_ON_EXTRACTED_SETUP_PATCH_FAILED = 127,
DROP_WRITE_ON_EXTRACTED_SETUP_FAILED = 128,
DROP_WRITE_ON_EXPANDED_SETUP_FAILED = 129,
};
} // namespace mini_installer
......
......@@ -8,7 +8,10 @@
namespace mini_installer {
MiniFile::MiniFile() = default;
MiniFile::MiniFile(DeleteOnClose delete_on_close)
: delete_on_close_flag_(delete_on_close != DeleteOnClose::kNo
? FILE_FLAG_DELETE_ON_CLOSE
: 0) {}
MiniFile::~MiniFile() {
Close();
......@@ -26,9 +29,11 @@ bool MiniFile::Create(const wchar_t* path) {
Close();
if (!path_.assign(path))
return false;
handle_ =
::CreateFileW(path_.get(), DELETE | GENERIC_WRITE, FILE_SHARE_DELETE,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
handle_ = ::CreateFileW(path_.get(), GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ,
/*lpSecurityAttributes=*/nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | delete_on_close_flag_,
/*hTemplateFile=*/nullptr);
if (handle_ != INVALID_HANDLE_VALUE)
return true;
path_.clear();
......@@ -39,11 +44,76 @@ bool MiniFile::IsValid() const {
return handle_ != INVALID_HANDLE_VALUE;
}
bool MiniFile::DeleteOnClose() {
FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/TRUE};
return IsValid() &&
::SetFileInformationByHandle(handle_, FileDispositionInfo,
&disposition, sizeof(disposition));
bool MiniFile::DropWritePermission() {
// The original file was opened with write access (of course), so it will take
// a little hoop jumping to get a handle without it. First, get a new handle
// that doesn't have write access. This one must allow others to write on
// account of the fact that the original handle has write access.
HANDLE without_write =
::ReOpenFile(handle_, GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ,
delete_on_close_flag_);
if (without_write == INVALID_HANDLE_VALUE) {
const auto error = ::GetLastError();
Close();
::SetLastError(error);
return false;
}
// Next, close the original handle so that there are no longer any writers.
// This will mark the file for deletion if the original handle was opened with
// FILE_FLAG_DELETE_ON_CLOSE.
::CloseHandle(std::exchange(handle_, INVALID_HANDLE_VALUE));
// Now unmark the file for deletion if needed.
if (delete_on_close_flag_) {
FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/FALSE};
if (!::SetFileInformationByHandle(without_write, FileDispositionInfo,
&disposition, sizeof(disposition))) {
const auto error = ::GetLastError();
::CloseHandle(std::exchange(without_write, INVALID_HANDLE_VALUE));
Close();
::SetLastError(error);
return false;
}
}
// Now open a read-only handle (with FILE_FLAG_DELETE_ON_CLOSE as needed) that
// doesn't allow others to write. Note that there is a potential race here:
// another party could open the file for shared write access at this precise
// moment, causing this ReOpenFile to fail. This would likely be an issue
// anyway, as one common thing to do with the file is to execute it, which
// will fail if there are writers.
handle_ =
::ReOpenFile(without_write, GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ, delete_on_close_flag_);
if (handle_ == INVALID_HANDLE_VALUE) {
const auto error = ::GetLastError();
::CloseHandle(std::exchange(without_write, INVALID_HANDLE_VALUE));
Close();
::SetLastError(error);
return false;
}
// Closing the handle that allowed shared writes may once again mark the file
// for deletion.
::CloseHandle(std::exchange(without_write, INVALID_HANDLE_VALUE));
// Everything went according to plan; |handle_| is now lacking write access
// and does not allow other writers. The last step is to unmark the file for
// deletion once again, as the closure of |without_write| has re-marked it.
if (delete_on_close_flag_) {
FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/FALSE};
if (!::SetFileInformationByHandle(handle_, FileDispositionInfo,
&disposition, sizeof(disposition))) {
const auto error = ::GetLastError();
Close();
::SetLastError(error);
return false;
}
}
return true;
}
void MiniFile::Close() {
......@@ -64,6 +134,18 @@ HANDLE MiniFile::DuplicateHandle() const {
: INVALID_HANDLE_VALUE;
}
bool MiniFile::Open(const PathString& path) {
Close();
handle_ = ::CreateFileW(path.get(), GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ,
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
delete_on_close_flag_, /*hTemplateFile=*/nullptr);
if (handle_ == INVALID_HANDLE_VALUE)
return false;
path_.assign(path);
return true;
}
HANDLE MiniFile::GetHandleUnsafe() const {
return handle_;
}
......
......@@ -14,10 +14,11 @@ namespace mini_installer {
// A simple abstraction over a path to a file and a Windows file handle to it.
class MiniFile {
public:
MiniFile();
enum class DeleteOnClose : bool { kNo = false, kYes = true };
explicit MiniFile(DeleteOnClose delete_on_close);
// Closes the file if the instance holds a valid handle. The file will be
// deleted if directed by a call to DeleteOnClose().
// deleted if the instance was constructed with |delete_on_close|.
~MiniFile();
MiniFile(const MiniFile&) = delete;
......@@ -35,17 +36,28 @@ class MiniFile {
// Returns true if this object has a path and a handle to an open file.
bool IsValid() const;
// Marks the file for deletion when the handle is closed via Close() or the
// instance's destructor. This state follows the handle when moved.
bool DeleteOnClose();
// Closes the handle and clears the path. Following this, IsValid() will
// return false.
// Drops write permission on the file handle so that other parties that
// require no writers may open the file. In particular, the Windows loader
// opens files for execution with shared read/delete access, as do the
// extraction operations in Chrome's mini_installer.exe and setup.exe. These
// would fail with sharing violations if mini_installer were to hold files
// open with write permissions. Returns false on failure, in which case the
// instance is no longer valid. The file will have been deleted if the
// instance was created with DeleteOnClose.
bool DropWritePermission();
// Closes the handle and clears the path. The file will be deleted if the
// instance was constructed with |delete_on_close|. Following this, IsValid()
// will return false.
void Close();
// Returns a new handle to the file, or INVALID_HANDLE_VALUE on error.
HANDLE DuplicateHandle() const;
// Opens the file for read access, disallowing writers (as if Create followed
// by DropWritePermission).
bool Open(const PathString& path);
// Returns the path to the open file, or a pointer to an empty string if
// IsValid() is false.
const wchar_t* path() const { return path_.get(); }
......@@ -61,6 +73,10 @@ class MiniFile {
// A handle to the open file, or INVALID_HANDLE_VALUE.
HANDLE handle_ = INVALID_HANDLE_VALUE;
// Zero or FILE_FLAG_DELETE_ON_CLOSE, according to how the instance was
// constructed.
const DWORD delete_on_close_flag_;
};
} // namespace mini_installer
......
......@@ -8,16 +8,25 @@
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/installer/mini_installer/path_string.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace mini_installer {
class MiniFileTest : public ::testing::Test {
// A test harness for MiniFile. The MiniFile::DeleteOnClose parameter is passed
// to the test instance's constructor.
class MiniFileTest : public ::testing::TestWithParam<MiniFile::DeleteOnClose> {
protected:
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
void TearDown() override { EXPECT_TRUE(temp_dir_.Delete()); }
static MiniFile::DeleteOnClose delete_on_close() { return GetParam(); }
static bool should_delete_on_close() {
return delete_on_close() != MiniFile::DeleteOnClose::kNo;
}
const base::FilePath& temp_dir() const { return temp_dir_.GetPath(); }
private:
......@@ -25,68 +34,73 @@ class MiniFileTest : public ::testing::Test {
};
// Create should create a file.
TEST_F(MiniFileTest, Create) {
TEST_P(MiniFileTest, Create) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
MiniFile file(delete_on_close());
ASSERT_TRUE(file.Create(file_path.value().c_str()));
ASSERT_TRUE(base::PathExists(file_path));
EXPECT_TRUE(base::PathExists(file_path));
}
// Created files should be deletable by others and should vanish when closed.
TEST_F(MiniFileTest, CreateDeleteIsShared) {
TEST_P(MiniFileTest, CreateDeleteIsShared) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
MiniFile file(delete_on_close());
ASSERT_TRUE(file.Create(file_path.value().c_str()));
// DeleteFile uses POSIX semantics, so the file appears to vanish immediately.
ASSERT_TRUE(base::DeleteFile(file_path));
file.Close();
ASSERT_FALSE(base::PathExists(file_path));
EXPECT_FALSE(base::PathExists(file_path));
}
// DeleteOnClose should work as advertised.
TEST_F(MiniFileTest, DeleteOnClose) {
// Tests that a file can be opened without shared write access after write
// permissions are dropped.
TEST_P(MiniFileTest, DropWritePermission) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
MiniFile file(delete_on_close());
ASSERT_TRUE(file.Create(file_path.value().c_str()));
ASSERT_TRUE(file.DeleteOnClose());
// The file can no longer be opened now that it has been marked for deletion.
// Attempts to do so will fail with ERROR_ACCESS_DENIED. Under the covers, the
// NT status code is STATUS_DELETE_PENDING. Since base::PathExists will return
// false in this case, confirm the file's existence by trying to open it.
base::File the_file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_SHARE_DELETE);
ASSERT_FALSE(the_file.IsValid());
ASSERT_EQ(the_file.error_details(), base::File::FILE_ERROR_ACCESS_DENIED);
file.Close();
ASSERT_FALSE(base::PathExists(file_path));
ASSERT_FALSE(base::File(file_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_WRITE |
base::File::FLAG_SHARE_DELETE)
.IsValid());
ASSERT_TRUE(file.DropWritePermission());
EXPECT_TRUE(base::File(file_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_WRITE |
base::File::FLAG_SHARE_DELETE)
.IsValid());
}
// Close should really close.
TEST_F(MiniFileTest, Close) {
TEST_P(MiniFileTest, CreateThenClose) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
MiniFile file(delete_on_close());
ASSERT_TRUE(file.Create(file_path.value().c_str()));
file.Close();
EXPECT_FALSE(file.IsValid());
EXPECT_EQ(*file.path(), 0);
ASSERT_TRUE(base::PathExists(file_path));
base::File f(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ |
base::File::FLAG_EXCLUSIVE_WRITE);
ASSERT_TRUE(f.IsValid());
ASSERT_NE(base::PathExists(file_path), should_delete_on_close());
if (!should_delete_on_close()) {
// If closing should not have deleted the file, it should now be possible to
// open it with exclusive access.
EXPECT_TRUE(base::File(file_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ |
base::File::FLAG_EXCLUSIVE_WRITE)
.IsValid());
}
}
// DuplicateHandle should work as advertized.
TEST_F(MiniFileTest, DuplicateHandle) {
TEST_P(MiniFileTest, DuplicateHandle) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
MiniFile file(delete_on_close());
ASSERT_TRUE(file.Create(file_path.value().c_str()));
HANDLE dup = file.DuplicateHandle();
ASSERT_NE(dup, INVALID_HANDLE_VALUE);
......@@ -104,10 +118,55 @@ TEST_F(MiniFileTest, DuplicateHandle) {
::CloseHandle(std::exchange(dup, INVALID_HANDLE_VALUE));
}
TEST_F(MiniFileTest, Path) {
// Open should provide access to a file just like Create, but for a file that
// already exists.
TEST_P(MiniFileTest, Open) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
ASSERT_TRUE(
base::File(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE)
.IsValid());
ASSERT_TRUE(base::PathExists(file_path));
PathString path_string;
ASSERT_TRUE(path_string.assign(file_path.value().c_str()));
MiniFile file(delete_on_close());
ASSERT_TRUE(file.Open(path_string));
}
// Close should really close.
TEST_P(MiniFileTest, OpenThenClose) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
ASSERT_TRUE(
base::File(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE)
.IsValid());
ASSERT_TRUE(base::PathExists(file_path));
MiniFile file(delete_on_close());
PathString path_string;
ASSERT_TRUE(path_string.assign(file_path.value().c_str()));
ASSERT_TRUE(file.Open(path_string));
file.Close();
EXPECT_FALSE(file.IsValid());
EXPECT_EQ(*file.path(), 0);
ASSERT_NE(base::PathExists(file_path), should_delete_on_close());
if (!should_delete_on_close()) {
// If closing should not have deleted the file, it should now be possible to
// open it with exclusive access.
EXPECT_TRUE(base::File(file_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ |
base::File::FLAG_EXCLUSIVE_WRITE)
.IsValid());
}
}
TEST_P(MiniFileTest, Path) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file(delete_on_close());
EXPECT_EQ(*file.path(), 0);
ASSERT_TRUE(file.Create(file_path.value().c_str()));
......@@ -117,10 +176,10 @@ TEST_F(MiniFileTest, Path) {
EXPECT_EQ(*file.path(), 0);
}
TEST_F(MiniFileTest, GetHandleUnsafe) {
TEST_P(MiniFileTest, GetHandleUnsafe) {
const base::FilePath file_path = temp_dir().Append(FILE_PATH_LITERAL("HUM"));
MiniFile file;
MiniFile file(delete_on_close());
EXPECT_EQ(file.GetHandleUnsafe(), INVALID_HANDLE_VALUE);
ASSERT_TRUE(file.Create(file_path.value().c_str()));
......@@ -130,4 +189,11 @@ TEST_F(MiniFileTest, GetHandleUnsafe) {
EXPECT_EQ(file.GetHandleUnsafe(), INVALID_HANDLE_VALUE);
}
INSTANTIATE_TEST_SUITE_P(DoNotDeleteOnClose,
MiniFileTest,
::testing::Values(MiniFile::DeleteOnClose::kNo));
INSTANTIATE_TEST_SUITE_P(DeleteOnClose,
MiniFileTest,
::testing::Values(MiniFile::DeleteOnClose::kYes));
} // namespace mini_installer
......@@ -8,6 +8,8 @@
#include "chrome/installer/mini_installer/mini_file.h"
namespace mini_installer {
PEResource::PEResource(HRSRC resource, HMODULE module)
: resource_(resource), module_(module) {}
......@@ -24,42 +26,43 @@ size_t PEResource::Size() {
return ::SizeofResource(module_, resource_);
}
bool PEResource::WriteToDisk(const wchar_t* full_path) {
bool PEResource::WriteToDisk(const wchar_t* full_path, MiniFile& file) {
// Resource handles are not real HGLOBALs so do not attempt to close them.
// Resources are freed when the containing module is unloaded.
HGLOBAL data_handle = ::LoadResource(module_, resource_);
if (nullptr == data_handle)
if (!data_handle)
return false;
const char* data = reinterpret_cast<const char*>(::LockResource(data_handle));
if (nullptr == data)
if (!data)
return false;
mini_installer::MiniFile file;
if (!file.Create(full_path))
return false;
// Don't write all of the data at once because this can lead to kernel
// address-space exhaustion on 32-bit Windows (see https://crbug.com/1001022
// for details).
constexpr size_t kMaxWriteAmount = 8 * 1024 * 1024;
const size_t resource_size = Size();
for (size_t total_written = 0; total_written < resource_size; /**/) {
const size_t write_amount =
constexpr DWORD kMaxWriteAmount = 8 * 1024 * 1024;
const DWORD resource_size = Size();
for (DWORD total_written = 0; total_written < resource_size; /**/) {
const DWORD write_amount =
std::min(kMaxWriteAmount, resource_size - total_written);
DWORD written = 0;
if (!::WriteFile(file.GetHandleUnsafe(), data + total_written,
static_cast<DWORD>(write_amount), &written, nullptr)) {
if (!::WriteFile(file.GetHandleUnsafe(), data + total_written, write_amount,
&written, nullptr)) {
const auto write_error = ::GetLastError();
// Delete the file since the write failed.
file.DeleteOnClose();
file.Close();
::SetLastError(write_error);
return false;
}
total_written += write_amount;
total_written += written;
}
return true;
}
} // namespace mini_installer
......@@ -9,6 +9,10 @@
#include <stddef.h>
namespace mini_installer {
class MiniFile;
// This class models a windows PE resource. It does not pretend to be a full
// API wrapper and it is just concerned with loading it to memory and writing
// it to disk. Each resource is unique only in the context of a loaded module,
......@@ -30,14 +34,17 @@ class PEResource {
// not valid.
size_t Size();
// Creates a file in 'path' with a copy of the resource. If the resource can
// not be loaded into memory or if it cannot be written to disk it returns
// false.
bool WriteToDisk(const wchar_t* path);
// Writes the resource to the file |path|. Returns true on success, in which
// case |file| holds an open handle to the destination file. |file| will be
// opened with exclusive write access and shared read and delete access, and
// will be marked as delete-on-close.
bool WriteToDisk(const wchar_t* path, MiniFile& file);
private:
HRSRC resource_;
HMODULE module_;
};
} // namespace mini_installer
#endif // CHROME_INSTALLER_MINI_INSTALLER_PE_RESOURCE_H_
// Copyright 2020 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/mini_installer/pe_resource.h"
#include <windows.h>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/installer/mini_installer/mini_file.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace mini_installer {
TEST(PEResourceTest, WriteToDisk) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath manifest_path =
temp_dir.GetPath().Append(FILE_PATH_LITERAL("manifest"));
PEResource manifest(MAKEINTRESOURCE(1), RT_MANIFEST,
::GetModuleHandle(nullptr));
ASSERT_TRUE(manifest.IsValid());
MiniFile manifest_file(MiniFile::DeleteOnClose::kYes);
ASSERT_TRUE(
manifest.WriteToDisk(manifest_path.value().c_str(), manifest_file));
ASSERT_TRUE(manifest_file.IsValid());
EXPECT_PRED1(base::PathExists, manifest_path);
manifest_file.Close();
EXPECT_FALSE(base::PathExists(manifest_path));
EXPECT_TRUE(temp_dir.Delete());
}
} // namespace mini_installer
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