Commit 4883031c authored by Greg Thompson's avatar Greg Thompson Committed by Commit Bot

Improve resource extraction in mini_installer.

This CL makes the following improvements to extraction of the chrome
archive and installer from the mini_installer:

- Better documentation on OnResourceFound
- Resources are only written to disk once all other operations have
  succeeded.
- The output file is deleted in case of mid-write error (e.g., disk
  full).
- Duplicate resources lead to failure (e.g., two resources named
  "chrome*").
- Error codes from write failures now bubble up to ExtraCode1.

BUG=516207

Change-Id: I9a3b69ee59421116a175914b1f2d16f9eb15b688
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2302629
Commit-Queue: Greg Thompson <grt@chromium.org>
Reviewed-by: default avatarWill Harris <wfh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789761}
parent 37c76527
...@@ -27,7 +27,7 @@ enum ExitCode { ...@@ -27,7 +27,7 @@ enum ExitCode {
UNABLE_TO_FIND_REGISTRY_KEY = 110, UNABLE_TO_FIND_REGISTRY_KEY = 110,
PATCH_NOT_FOR_INSTALLED_VERSION = 111, PATCH_NOT_FOR_INSTALLED_VERSION = 111,
UNABLE_TO_EXTRACT_CHROME_ARCHIVE = 112, UNABLE_TO_EXTRACT_CHROME_ARCHIVE = 112,
UNABLE_TO_EXTRACT_SETUP_BL = 113, // UNABLE_TO_EXTRACT_SETUP_BL = 113,
// UNABLE_TO_EXTRACT_SETUP_BN = 114, // UNABLE_TO_EXTRACT_SETUP_BN = 114,
UNABLE_TO_EXTRACT_SETUP_EXE = 115, UNABLE_TO_EXTRACT_SETUP_EXE = 115,
UNABLE_TO_EXTRACT_SETUP = 116, UNABLE_TO_EXTRACT_SETUP = 116,
......
...@@ -58,6 +58,8 @@ struct Context { ...@@ -58,6 +58,8 @@ struct Context {
PathString* chrome_resource_path; PathString* chrome_resource_path;
// Second output from call back method. Full path of Setup archive/exe. // Second output from call back method. Full path of Setup archive/exe.
PathString* setup_resource_path; PathString* setup_resource_path;
// A Windows error code corresponding to an extraction error.
DWORD error_code;
}; };
// TODO(grt): Frame this in terms of whether or not the brand supports // TODO(grt): Frame this in terms of whether or not the brand supports
...@@ -275,39 +277,56 @@ void AppendCommandLineFlags(const wchar_t* command_line, ...@@ -275,39 +277,56 @@ void AppendCommandLineFlags(const wchar_t* command_line,
buffer->append(command_line); buffer->append(command_line);
} }
// Windows defined callback used in the EnumResourceNames call. For each // Processes a resource of type |type| in |module| on behalf of a call to
// matching resource found, the callback is invoked and at this point we write // EnumResourceNames. On each call, |name| contains the name of a resource. A
// it to disk. We expect resource names to start with 'chrome' or 'setup'. Any // TRUE return value continues the enumeration, whereas FALSE stops it. This
// other name is treated as an error. // function extracts the first resource starting with "chrome" and/or "setup",
// populating |context| (which must be a pointer to a Context struct) with the
// path(s) of the extracted file(s). Enumeration stops early in case of error,
// which includes any unexpected resources or duplicate matching resources.
// |context|'s |error_code| member may be populated with a Windows error code
// corresponding to an error condition.
BOOL CALLBACK OnResourceFound(HMODULE module, BOOL CALLBACK OnResourceFound(HMODULE module,
const wchar_t* type, const wchar_t* type,
wchar_t* name, wchar_t* name,
LONG_PTR context) { LONG_PTR l_param) {
if (!context) if (!l_param)
return FALSE; return FALSE; // Break: impossible condition.
if (IS_INTRESOURCE(name))
return FALSE; // Break: resources with integer names are unexpected.
Context* ctx = reinterpret_cast<Context*>(context); Context& context = *reinterpret_cast<Context*>(l_param);
PEResource resource(name, type, module); PEResource resource(name, type, module);
if (!resource.IsValid() || resource.Size() < 1) if (!resource.IsValid() || resource.Size() < 1)
return FALSE; return FALSE; // Break: invalid/empty resources are unexpected.
PathString full_path; PathString full_path;
if (!full_path.assign(ctx->base_path) || !full_path.append(name) || if (!full_path.assign(context.base_path) || !full_path.append(name))
!resource.WriteToDisk(full_path.get())) return FALSE; // Break: failed to form the output path.
return FALSE;
if (StrStartsWith(name, kChromeArchivePrefix) &&
if (StrStartsWith(name, kChromeArchivePrefix)) { context.chrome_resource_path->empty()) {
ctx->chrome_resource_path->assign(full_path); if (!resource.WriteToDisk(full_path.get())) {
} else if (StrStartsWith(name, kSetupPrefix)) { context.error_code = ::GetLastError();
ctx->setup_resource_path->assign(full_path); return FALSE; // Break: failed to write resource.
}
context.chrome_resource_path->assign(full_path);
} else if (StrStartsWith(name, kSetupPrefix) &&
context.setup_resource_path->empty()) {
if (!resource.WriteToDisk(full_path.get())) {
context.error_code = ::GetLastError();
return FALSE; // Break: failed to write resource.
}
context.setup_resource_path->assign(full_path);
} else { } else {
// Resources should either start with 'chrome' or 'setup'. We don't handle // Break: unexpected resource names or multiple {chrome,setup}* resources
// anything else. // are unexpected.
return FALSE; return FALSE;
} }
return TRUE; return TRUE; // Continue: advance to the next resource.
} }
#if defined(COMPONENT_BUILD) #if defined(COMPONENT_BUILD)
...@@ -354,18 +373,21 @@ ProcessExitResult UnpackBinaryResources(const Configuration& configuration, ...@@ -354,18 +373,21 @@ ProcessExitResult UnpackBinaryResources(const Configuration& configuration,
base_path, base_path,
archive_path, archive_path,
setup_path, setup_path,
ERROR_SUCCESS,
}; };
// Get the resources of type 'B7' (7zip archive). // Get the resources of type 'B7' (7zip archive).
// We need a chrome archive to do the installation. So if there // We need a chrome archive to do the installation. So if there
// is a problem in fetching B7 resource, just return an error. // is a problem in fetching B7 resource, just return an error.
if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound, if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound,
reinterpret_cast<LONG_PTR>(&context))) { reinterpret_cast<LONG_PTR>(&context)) ||
archive_path->empty()) {
const DWORD enum_error = ::GetLastError();
return ProcessExitResult(UNABLE_TO_EXTRACT_CHROME_ARCHIVE, return ProcessExitResult(UNABLE_TO_EXTRACT_CHROME_ARCHIVE,
::GetLastError()); enum_error == ERROR_RESOURCE_ENUM_USER_STOP
? context.error_code
: enum_error);
} }
if (archive_path->empty())
return ProcessExitResult(UNABLE_TO_EXTRACT_CHROME_ARCHIVE);
ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE); ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
...@@ -410,13 +432,15 @@ ProcessExitResult UnpackBinaryResources(const Configuration& configuration, ...@@ -410,13 +432,15 @@ ProcessExitResult UnpackBinaryResources(const Configuration& configuration,
// setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL' // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL'
// (compressed setup). // (compressed setup).
context.error_code = ERROR_SUCCESS;
if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound, if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound,
reinterpret_cast<LONG_PTR>(&context))) { reinterpret_cast<LONG_PTR>(&context)) ||
return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP_BL, ::GetLastError()); setup_path->empty()) {
} const DWORD enum_error = ::GetLastError();
if (setup_path->empty()) { return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP,
// Neither setup_patch.packed.7z nor setup.ex_ was found. enum_error == ERROR_RESOURCE_ENUM_USER_STOP
return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP); ? context.error_code
: enum_error);
} }
// Uncompress LZ compressed resource. Setup is packed with 'MSCF' // Uncompress LZ compressed resource. Setup is packed with 'MSCF'
......
...@@ -34,8 +34,9 @@ bool PEResource::WriteToDisk(const wchar_t* full_path) { ...@@ -34,8 +34,9 @@ bool PEResource::WriteToDisk(const wchar_t* full_path) {
return false; return false;
const size_t resource_size = Size(); const size_t resource_size = Size();
HANDLE out_file = ::CreateFile(full_path, GENERIC_WRITE, 0, nullptr, HANDLE out_file =
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); ::CreateFile(full_path, DELETE | GENERIC_WRITE, FILE_SHARE_DELETE,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (INVALID_HANDLE_VALUE == out_file) if (INVALID_HANDLE_VALUE == out_file)
return false; return false;
...@@ -49,7 +50,15 @@ bool PEResource::WriteToDisk(const wchar_t* full_path) { ...@@ -49,7 +50,15 @@ bool PEResource::WriteToDisk(const wchar_t* full_path) {
DWORD written = 0; DWORD written = 0;
if (!::WriteFile(out_file, data + total_written, if (!::WriteFile(out_file, data + total_written,
static_cast<DWORD>(write_amount), &written, nullptr)) { static_cast<DWORD>(write_amount), &written, nullptr)) {
const auto write_error = ::GetLastError();
// Delete the file since the write failed.
FILE_DISPOSITION_INFO disposition = {/*DeleteFile=*/TRUE};
::SetFileInformationByHandle(out_file, FileDispositionInfo, &disposition,
sizeof(disposition));
::CloseHandle(out_file); ::CloseHandle(out_file);
::SetLastError(write_error);
return false; return false;
} }
total_written += write_amount; total_written += write_amount;
......
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