blob: 5ed402f1828b9fc96bd21a1ce92183424108ed4b [file] [log] [blame]
// Copyright (c) 2012 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/lzma_util.h"
#include <stddef.h>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/installer/util/lzma_file_allocator.h"
extern "C" {
#include "third_party/lzma_sdk/7z.h"
#include "third_party/lzma_sdk/7zAlloc.h"
#include "third_party/lzma_sdk/7zCrc.h"
#include "third_party/lzma_sdk/7zFile.h"
}
namespace {
SRes LzmaReadFile(HANDLE file, void* data, size_t* size) {
if (*size == 0)
return SZ_OK;
size_t processedSize = 0;
DWORD maxSize = *size;
do {
DWORD processedLoc = 0;
BOOL res = ReadFile(file, data, maxSize, &processedLoc, NULL);
data = (void*)((unsigned char*)data + processedLoc);
maxSize -= processedLoc;
processedSize += processedLoc;
if (processedLoc == 0) {
if (res)
return SZ_ERROR_READ;
else
break;
}
} while (maxSize > 0);
*size = processedSize;
return SZ_OK;
}
SRes SzFileSeekImp(void* object, Int64* pos, ESzSeek origin) {
CFileInStream* s = (CFileInStream*)object;
LARGE_INTEGER value;
value.LowPart = (DWORD)*pos;
value.HighPart = (LONG)((UInt64)*pos >> 32);
DWORD moveMethod;
switch (origin) {
case SZ_SEEK_SET:
moveMethod = FILE_BEGIN;
break;
case SZ_SEEK_CUR:
moveMethod = FILE_CURRENT;
break;
case SZ_SEEK_END:
moveMethod = FILE_END;
break;
default:
return SZ_ERROR_PARAM;
}
value.LowPart = SetFilePointer(s->file.handle, value.LowPart, &value.HighPart,
moveMethod);
*pos = ((Int64)value.HighPart << 32) | value.LowPart;
return ((value.LowPart == 0xFFFFFFFF) && (GetLastError() != ERROR_SUCCESS))
? SZ_ERROR_FAIL
: SZ_OK;
}
SRes SzFileReadImp(void* object, void* buffer, size_t* size) {
CFileInStream* s = (CFileInStream*)object;
return LzmaReadFile(s->file.handle, buffer, size);
}
// Returns EXCEPTION_EXECUTE_HANDLER and populates |status| with the underlying
// NTSTATUS code for paging errors encountered while accessing file-backed
// mapped memory. Otherwise, return EXCEPTION_CONTINUE_SEARCH.
DWORD FilterPageError(const LzmaFileAllocator& file_allocator,
DWORD exception_code,
const EXCEPTION_POINTERS* info,
int32_t* status) {
if (exception_code != EXCEPTION_IN_PAGE_ERROR)
return EXCEPTION_CONTINUE_SEARCH;
const EXCEPTION_RECORD* exception_record = info->ExceptionRecord;
if (!file_allocator.IsAddressMapped(
exception_record->ExceptionInformation[1])) {
return EXCEPTION_CONTINUE_SEARCH;
}
// Cast NTSTATUS to int32_t to avoid including winternl.h
*status = exception_record->ExceptionInformation[2];
return EXCEPTION_EXECUTE_HANDLER;
}
} // namespace
DWORD UnPackArchive(const base::FilePath& archive,
const base::FilePath& output_dir,
base::FilePath* output_file,
UnPackStatus* unpack_status,
int32_t* ntstatus) {
VLOG(1) << "Opening archive " << archive.value();
LzmaUtilImpl lzma_util;
DWORD ret;
if ((ret = lzma_util.OpenArchive(archive)) != ERROR_SUCCESS) {
PLOG(ERROR) << "Unable to open install archive: " << archive.value();
} else {
VLOG(1) << "Uncompressing archive to path " << output_dir.value();
if ((ret = lzma_util.UnPack(output_dir, output_file)) != ERROR_SUCCESS)
PLOG(ERROR) << "Unable to uncompress archive: " << archive.value();
lzma_util.CloseArchive();
}
if (unpack_status)
*unpack_status = lzma_util.GetUnPackStatus();
if (ntstatus)
*ntstatus = lzma_util.GetNTSTATUSCode();
return ret;
}
LzmaUtilImpl::LzmaUtilImpl() {}
LzmaUtilImpl::~LzmaUtilImpl() {
CloseArchive();
}
DWORD LzmaUtilImpl::OpenArchive(const base::FilePath& archivePath) {
// Make sure file is not already open.
CloseArchive();
DWORD ret = ERROR_SUCCESS;
archive_handle_ =
CreateFile(archivePath.value().c_str(), GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (archive_handle_ == INVALID_HANDLE_VALUE) {
archive_handle_ = NULL; // The rest of the code only checks for NULL.
ret = GetLastError();
if (ret == ERROR_FILE_NOT_FOUND)
unpack_status_ = UNPACK_ARCHIVE_NOT_FOUND;
else
unpack_status_ = UNPACK_ARCHIVE_CANNOT_OPEN;
}
return ret;
}
DWORD LzmaUtilImpl::UnPack(const base::FilePath& location) {
return UnPack(location, NULL);
}
DWORD LzmaUtilImpl::UnPack(const base::FilePath& location,
base::FilePath* output_file) {
if (!archive_handle_)
return ERROR_INVALID_HANDLE;
CFileInStream archiveStream;
CLookToRead lookStream;
CSzArEx db;
ISzAlloc allocImp;
ISzAlloc allocTempImp;
DWORD ret = ERROR_SUCCESS;
SRes sz_res = SZ_OK;
archiveStream.file.handle = archive_handle_;
archiveStream.s.Read = SzFileReadImp;
archiveStream.s.Seek = SzFileSeekImp;
LookToRead_CreateVTable(&lookStream, false);
lookStream.realStream = &archiveStream.s;
allocImp.Alloc = SzAlloc;
allocImp.Free = SzFree;
allocTempImp.Alloc = SzAllocTemp;
allocTempImp.Free = SzFreeTemp;
CrcGenerateTable();
SzArEx_Init(&db);
if ((sz_res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp)) !=
SZ_OK) {
LOG(ERROR) << L"Error returned by SzArchiveOpen: " << sz_res;
unpack_status_ = UNPACK_SZAREX_OPEN_ERROR;
return ERROR_INVALID_HANDLE;
}
Byte* outBuffer = 0; // it must be 0 before first call for each new archive
UInt32 blockIndex = 0xFFFFFFFF; // can have any value if outBuffer = 0
size_t outBufferSize = 0; // can have any value if outBuffer = 0
LzmaFileAllocator fileAllocator(location);
unpack_status_ = UNPACK_NO_ERROR;
for (unsigned int i = 0; i < db.NumFiles; i++) {
DWORD written;
size_t offset = 0;
size_t outSizeProcessed = 0;
int32_t status = 0; // STATUS_SUCCESS
__try {
if ((sz_res =
SzArEx_Extract(&db, &lookStream.s, i, &blockIndex, &outBuffer,
&outBufferSize, &offset, &outSizeProcessed,
&fileAllocator, &allocTempImp)) != SZ_OK) {
LOG(ERROR) << L"Error returned by SzExtract: " << sz_res;
ret = ERROR_INVALID_HANDLE;
unpack_status_ = UNPACK_EXTRACT_ERROR;
}
} __except(FilterPageError(fileAllocator, GetExceptionCode(),
GetExceptionInformation(), &status)) {
ret = ERROR_IO_DEVICE;
ntstatus_ = status;
LOG(ERROR) << L"EXCEPTION_IN_PAGE_ERROR while accessing mapped memory; "
L"NTSTATUS = "
<< ntstatus_;
unpack_status_ = UNPACK_EXTRACT_EXCEPTION;
}
if (ret != ERROR_SUCCESS)
break;
size_t file_name_length = SzArEx_GetFileNameUtf16(&db, i, NULL);
if (file_name_length < 1) {
LOG(ERROR) << L"Couldn't get file name";
ret = ERROR_INVALID_HANDLE;
unpack_status_ = UNPACK_NO_FILENAME_ERROR;
break;
}
std::vector<UInt16> file_name(file_name_length);
SzArEx_GetFileNameUtf16(&db, i, &file_name[0]);
// |file_name| is NULL-terminated.
base::FilePath file_path = location.Append(
base::FilePath::StringType(file_name.begin(), file_name.end() - 1));
if (output_file)
*output_file = file_path;
// If archive entry is directory create it and move on to the next entry.
if (SzArEx_IsDir(&db, i)) {
CreateDirectory(file_path);
continue;
}
CreateDirectory(file_path.DirName());
HANDLE hFile;
hFile = CreateFile(file_path.value().c_str(), GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
ret = GetLastError();
LOG(ERROR) << L"Error returned by CreateFile: " << ret;
unpack_status_ = UNPACK_CREATE_FILE_ERROR;
break;
}
if ((!WriteFile(hFile, outBuffer + offset, (DWORD)outSizeProcessed,
&written, NULL)) ||
(written != outSizeProcessed)) {
ret = GetLastError();
PLOG(ERROR) << L"Error returned by WriteFile";
CloseHandle(hFile);
unpack_status_ = UNPACK_WRITE_FILE_ERROR;
break;
}
if (SzBitWithVals_Check(&db.MTime, i)) {
if (!SetFileTime(hFile, NULL, NULL,
(const FILETIME*)(&db.MTime.Vals[i]))) {
ret = GetLastError();
PLOG(ERROR) << L"Error returned by SetFileTime";
CloseHandle(hFile);
unpack_status_ = UNPACK_SET_FILE_TIME_ERROR;
break;
}
}
if (!CloseHandle(hFile)) {
ret = GetLastError();
PLOG(ERROR) << L"Error returned by CloseHandle";
unpack_status_ = UNPACK_CLOSE_FILE_ERROR;
break;
}
} // for loop
IAlloc_Free(&fileAllocator, outBuffer);
SzArEx_Free(&db, &allocImp);
DCHECK_EQ(ret == static_cast<DWORD>(ERROR_SUCCESS),
unpack_status_ == UNPACK_NO_ERROR);
return ret;
}
void LzmaUtilImpl::CloseArchive() {
if (archive_handle_) {
CloseHandle(archive_handle_);
archive_handle_ = NULL;
}
}
bool LzmaUtilImpl::CreateDirectory(const base::FilePath& dir) {
bool ret = true;
if (directories_created_.find(dir.value()) == directories_created_.end()) {
ret = base::CreateDirectory(dir);
if (ret)
directories_created_.insert(dir.value());
}
return ret;
}