| // Copyright (c) 2013 The Chromium OS 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 "memento_common.h" |
| #include "diskimage_installer.h" |
| |
| #define __STDC_FORMAT_MACROS // PRId64 |
| #include <inttypes.h> |
| #include <stdlib.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <sstream> |
| |
| using std::string; |
| using std::stringstream; |
| |
| namespace chromeos_memento_updater { |
| |
| DiskImageInstaller::DiskImageInstaller(const string& running_dir, |
| const string& board, |
| const string& image_path, |
| DiskImageInstallFormat format) |
| : GeneralInstaller(running_dir, board) { |
| image_path_ = image_path; |
| format_ = format; |
| allow_partition_options_ = true; |
| } |
| |
| DiskImageInstaller::~DiskImageInstaller() {} |
| |
| int DiskImageInstaller::DecompressImage(FILE** fp) { |
| // TODO(chunyen): We may want to rewrite this with zlib, to reduce the |
| // required disk space, and to support file not end with .zip. |
| const string kZip = ".zip"; |
| size_t path_length = image_path_.length(); |
| // Return if file path does not end with .zip |
| if (path_length <= kZip.length() || |
| image_path_.compare(path_length - kZip.length(), |
| kZip.length(), kZip) != 0) { |
| return error("The path of file in zip format must ends with .zip"); |
| } |
| // Unzip with gzip. |
| if (system(("gzip -d -f -S .zip " + image_path_).c_str()) != 0) { |
| return error("Fail to unzip image"); |
| } |
| // Open the unzipped file (the .zip is removed by gzip). |
| *fp = fopen(image_path_.substr(0, path_length - kZip.length()).c_str(), "r"); |
| format_ = kImage; |
| image_path_ = image_path_.substr(0, path_length - kZip.length()); |
| return 0; |
| } |
| |
| int DiskImageInstaller::PrepareImage(const int channel_id, |
| FILE* out_pipe) { |
| FILE* fp; |
| switch (format_) { |
| case kZip: |
| if (DecompressImage(&fp) != 0) { |
| return error("Fail to decompress image"); |
| }; |
| break; |
| case kImage: |
| fp = fopen(image_path_.c_str(), "rb"); |
| if (fp == NULL) { |
| return error("Fail to read image"); |
| } |
| break; |
| default: |
| return error("Unrecognizable format"); |
| } |
| |
| int64_t offset = GetImageOffsetInBlock(image_path_, channel_id) * kBlockSize; |
| int64_t size = GetImageSizeInBlock(image_path_, channel_id) * kBlockSize; |
| if (offset < 0 || size < 0) { |
| return error("Fail to read cgpt header"); |
| } |
| if (fseek(fp, offset, SEEK_SET) !=0 ) { |
| fclose(fp); |
| return error("Fail to fseek"); |
| } |
| int64_t total_read = 0; |
| int bytes_read; |
| int bytes_write; |
| const int64_t kBufSize = 4 * 1024 * 1024; // 4 MiB |
| char* buffer = new char[kBufSize]; |
| while ((bytes_read = |
| fread(buffer, 1 ,std::min(kBufSize, size - total_read), fp)) > 0) { |
| total_read += bytes_read; |
| bytes_write = 0; |
| do { |
| bytes_write += fwrite(&buffer[bytes_write], 1, |
| bytes_read - bytes_write, out_pipe); |
| } while(!ferror(out_pipe) && bytes_write < bytes_read); |
| } |
| delete[] buffer; |
| if (ferror(fp)) { |
| fclose(fp); |
| return error("Error reading image"); |
| } |
| fclose(fp); |
| if (ferror(out_pipe)) { |
| return error("Error writing to pipe"); |
| } |
| return 0; |
| } |
| |
| // TODO(chunyen): Call vboot_reference/cgpt when it has a library. |
| int64_t DiskImageInstaller::GetImageOffsetInBlock(const string& image_path, |
| const int partition) { |
| FILE* fp; |
| int64_t offset; |
| stringstream command; |
| |
| command << "cgpt show -b -i " << partition << " " << image_path; |
| fp = popen(command.str().c_str(), "r"); |
| if (fp == NULL) { |
| return -1; |
| } |
| if (fscanf(fp, "%"PRId64"", &offset) != 1) { |
| pclose(fp); |
| return -1; |
| } |
| pclose(fp); |
| |
| return offset; |
| } |
| |
| int64_t DiskImageInstaller::GetImageSizeInBlock(const string& image_path, |
| const int partition) { |
| FILE* fp; |
| int64_t size; |
| stringstream command; |
| |
| command << "cgpt show -s -i " << partition << " " << image_path; |
| fp = popen(command.str().c_str(), "r"); |
| if (fp == NULL) { |
| return -1; |
| } |
| if (fscanf(fp, "%"PRId64"", &size) != 1) { |
| pclose(fp); |
| return -1; |
| } |
| pclose(fp); |
| |
| return size; |
| } |
| |
| } // namespace chromeos_memento_updater |