1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/installer/util/lzma_util.h"
6
7#include "base/files/file_util.h"
8#include "base/logging.h"
9#include "base/strings/utf_string_conversions.h"
10
11extern "C" {
12#include "third_party/lzma_sdk/7z.h"
13#include "third_party/lzma_sdk/7zAlloc.h"
14#include "third_party/lzma_sdk/7zCrc.h"
15#include "third_party/lzma_sdk/7zFile.h"
16}
17
18
19namespace {
20
21SRes LzmaReadFile(HANDLE file, void *data, size_t *size) {
22  if (*size == 0)
23    return SZ_OK;
24
25  size_t processedSize = 0;
26  DWORD maxSize = *size;
27  do {
28    DWORD processedLoc = 0;
29    BOOL res = ReadFile(file, data, maxSize, &processedLoc, NULL);
30    data = (void *)((unsigned char *) data + processedLoc);
31    maxSize -= processedLoc;
32    processedSize += processedLoc;
33    if (processedLoc == 0) {
34      if (res)
35        return SZ_ERROR_READ;
36      else
37        break;
38    }
39  } while (maxSize > 0);
40
41  *size = processedSize;
42  return SZ_OK;
43}
44
45SRes SzFileSeekImp(void *object, Int64 *pos, ESzSeek origin) {
46  CFileInStream *s = (CFileInStream *) object;
47  LARGE_INTEGER value;
48  value.LowPart = (DWORD) *pos;
49  value.HighPart = (LONG) ((UInt64) *pos >> 32);
50  DWORD moveMethod;
51  switch (origin) {
52    case SZ_SEEK_SET:
53      moveMethod = FILE_BEGIN;
54      break;
55    case SZ_SEEK_CUR:
56      moveMethod = FILE_CURRENT;
57      break;
58    case SZ_SEEK_END:
59      moveMethod = FILE_END;
60      break;
61    default:
62      return SZ_ERROR_PARAM;
63  }
64  value.LowPart = SetFilePointer(s->file.handle, value.LowPart, &value.HighPart,
65                                 moveMethod);
66  *pos = ((Int64)value.HighPart << 32) | value.LowPart;
67  return ((value.LowPart == 0xFFFFFFFF) && (GetLastError() != NO_ERROR)) ?
68      SZ_ERROR_FAIL : SZ_OK;
69}
70
71SRes SzFileReadImp(void *object, void *buffer, size_t *size) {
72  CFileInStream *s = (CFileInStream *) object;
73  return LzmaReadFile(s->file.handle, buffer, size);
74}
75
76}  // namespace
77
78// static
79int32 LzmaUtil::UnPackArchive(const std::wstring& archive,
80                             const std::wstring& output_dir,
81                             std::wstring* output_file) {
82  VLOG(1) << "Opening archive " << archive;
83  LzmaUtil lzma_util;
84  DWORD ret;
85  if ((ret = lzma_util.OpenArchive(archive)) != NO_ERROR) {
86    LOG(ERROR) << "Unable to open install archive: " << archive
87               << ", error: " << ret;
88  } else {
89    VLOG(1) << "Uncompressing archive to path " << output_dir;
90    if ((ret = lzma_util.UnPack(output_dir, output_file)) != NO_ERROR) {
91      LOG(ERROR) << "Unable to uncompress archive: " << archive
92                 << ", error: " << ret;
93    }
94    lzma_util.CloseArchive();
95  }
96
97  return ret;
98}
99
100LzmaUtil::LzmaUtil() : archive_handle_(NULL) {}
101
102LzmaUtil::~LzmaUtil() {
103  CloseArchive();
104}
105
106DWORD LzmaUtil::OpenArchive(const std::wstring& archivePath) {
107  // Make sure file is not already open.
108  CloseArchive();
109
110  DWORD ret = NO_ERROR;
111  archive_handle_ = CreateFile(archivePath.c_str(), GENERIC_READ,
112      FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
113  if (archive_handle_ == INVALID_HANDLE_VALUE) {
114    archive_handle_ = NULL;  // The rest of the code only checks for NULL.
115    ret = GetLastError();
116  }
117  return ret;
118}
119
120DWORD LzmaUtil::UnPack(const std::wstring& location) {
121  return UnPack(location, NULL);
122}
123
124DWORD LzmaUtil::UnPack(const std::wstring& location,
125                       std::wstring* output_file) {
126  if (!archive_handle_)
127    return ERROR_INVALID_HANDLE;
128
129  CFileInStream archiveStream;
130  CLookToRead lookStream;
131  CSzArEx db;
132  ISzAlloc allocImp;
133  ISzAlloc allocTempImp;
134  DWORD ret = NO_ERROR;
135
136  archiveStream.file.handle = archive_handle_;
137  archiveStream.s.Read = SzFileReadImp;
138  archiveStream.s.Seek = SzFileSeekImp;
139  LookToRead_CreateVTable(&lookStream, false);
140  lookStream.realStream = &archiveStream.s;
141
142  allocImp.Alloc = SzAlloc;
143  allocImp.Free = SzFree;
144  allocTempImp.Alloc = SzAllocTemp;
145  allocTempImp.Free = SzFreeTemp;
146
147  CrcGenerateTable();
148  SzArEx_Init(&db);
149  if ((ret = SzArEx_Open(&db, &lookStream.s,
150                         &allocImp, &allocTempImp)) != SZ_OK) {
151    LOG(ERROR) << L"Error returned by SzArchiveOpen: " << ret;
152    return ERROR_INVALID_HANDLE;
153  }
154
155  Byte *outBuffer = 0; // it must be 0 before first call for each new archive
156  UInt32 blockIndex = 0xFFFFFFFF; // can have any value if outBuffer = 0
157  size_t outBufferSize = 0;  // can have any value if outBuffer = 0
158
159  for (unsigned int i = 0; i < db.db.NumFiles; i++) {
160    DWORD written;
161    size_t offset;
162    size_t outSizeProcessed;
163    CSzFileItem *f = db.db.Files + i;
164
165    if ((ret = SzArEx_Extract(&db, &lookStream.s, i, &blockIndex,
166                         &outBuffer, &outBufferSize, &offset, &outSizeProcessed,
167                         &allocImp, &allocTempImp)) != SZ_OK) {
168      LOG(ERROR) << L"Error returned by SzExtract: " << ret;
169      ret = ERROR_INVALID_HANDLE;
170      break;
171    }
172
173    size_t file_name_length = SzArEx_GetFileNameUtf16(&db, i, NULL);
174    if (file_name_length < 1) {
175      LOG(ERROR) << L"Couldn't get file name";
176      ret = ERROR_INVALID_HANDLE;
177      break;
178    }
179    std::vector<UInt16> file_name(file_name_length);
180    SzArEx_GetFileNameUtf16(&db, i, &file_name[0]);
181    // |file_name| is NULL-terminated.
182    base::FilePath file_path = base::FilePath(location).Append(
183        base::FilePath::StringType(file_name.begin(), file_name.end() - 1));
184
185    if (output_file)
186      *output_file = file_path.value();
187
188    // If archive entry is directory create it and move on to the next entry.
189    if (f->IsDir) {
190      CreateDirectory(file_path);
191      continue;
192    }
193
194    CreateDirectory(file_path.DirName());
195
196    HANDLE hFile;
197    hFile = CreateFile(file_path.value().c_str(), GENERIC_WRITE, 0, NULL,
198                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
199    if (hFile == INVALID_HANDLE_VALUE)  {
200      ret = GetLastError();
201      LOG(ERROR) << L"Error returned by CreateFile: " << ret;
202      break;
203    }
204
205    if ((!WriteFile(hFile, outBuffer + offset, (DWORD) outSizeProcessed,
206                    &written, NULL)) ||
207        (written != outSizeProcessed)) {
208      ret = GetLastError();
209      CloseHandle(hFile);
210      LOG(ERROR) << L"Error returned by WriteFile: " << ret;
211      break;
212    }
213
214    if (f->MTimeDefined) {
215      if (!SetFileTime(hFile, NULL, NULL,
216                       (const FILETIME *)&(f->MTime))) {
217        ret = GetLastError();
218        CloseHandle(hFile);
219        LOG(ERROR) << L"Error returned by SetFileTime: " << ret;
220        break;
221      }
222    }
223    if (!CloseHandle(hFile)) {
224      ret = GetLastError();
225      LOG(ERROR) << L"Error returned by CloseHandle: " << ret;
226      break;
227    }
228  }  // for loop
229
230  IAlloc_Free(&allocImp, outBuffer);
231  SzArEx_Free(&db, &allocImp);
232  return ret;
233}
234
235void LzmaUtil::CloseArchive() {
236  if (archive_handle_) {
237    CloseHandle(archive_handle_);
238    archive_handle_ = NULL;
239  }
240}
241
242bool LzmaUtil::CreateDirectory(const base::FilePath& dir) {
243  bool ret = true;
244  if (directories_created_.find(dir.value()) == directories_created_.end()) {
245    ret = base::CreateDirectory(dir);
246    if (ret)
247      directories_created_.insert(dir.value());
248  }
249  return ret;
250}
251