blob: 67333697797d697353d8c034b78bf026b44e6261 [file] [log] [blame]
// 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
using std::cerr;
using std::endl;
using std::string;
using std::stringstream;
const int kStatefulPartition = 1;
const int kRootfsPartition = 3;
const int kOemPartition = 8;
const int kEfiPartition = 12;
// In recovery image, the release kernel is at partition 4
// But in real OS kernel, it is at partition 2
const int kRecoveryKernel = 4;
const int kRealKernel = 2;
const string kConfigPath = "recovery.conf";
const string kImagePath = "release_image.bin";
const string kZipImagePath = "release_image.bin.zip";
const string kConfigUrl =
"https://dl.google.com/dl/edgedl/chromeos/recovery/recovery.conf";
void error_exit(const string& error_string)
{
cerr << "ERROR:" << error_string << endl;
exit(1);
}
string get_output_device(int partition)
{
// Determine output device by hardware architecture(x86 or arm)
char arch[10];
FILE* fp = popen("crossystem arch", "r");
fscanf(fp, "%5s", arch);
pclose(fp);
stringstream device_buffer;
string arch_string(arch);
if (arch_string.find("x86") != string::npos) {
device_buffer << "/dev/sda";
if (partition >= 0) {
device_buffer << partition;
}
} else if (arch_string.find("arm") != string::npos) {
device_buffer << "/dev/mmcblk0";
if (partition >= 0) {
device_buffer << "p" << partition;
}
} else {
// Fail to detect architecture
error_exit("Failed to auto detect architecture.");
}
return device_buffer.str();
}
string mount_partition(int partition,
const string& mount_option="")
{
// Mount with the specified partition with option.
string partition_name;
char mount_template[] = "mount_XXXXXX";
string mount_point(mkdtemp(mount_template));
mount_point += "/";
partition_name = get_output_device(partition);
stringstream mount_command;
mount_command << "mount " << mount_option << " " << partition_name <<
" " << mount_point;
cerr << mount_command.str() << endl;
if (system(mount_command.str().c_str()) != 0) {
error_exit("Failed to mount " + partition_name + ".");
}
return mount_point;
}
void unmount_partition(const string& mount_point)
{
// Unmount stateful partition
if (system(("umount " + mount_point).c_str()) != 0) {
error_exit("Failed to ummount " + mount_point + ".");
};
// Remove temp directory
if (remove(mount_point.c_str()) != 0) {
error_exit("Unable to remove temporary mount point.");
}
}
bool find_key_value(const string& line, const string& key, string& value)
{
if (line.find(key) == 0) {
int equal_pos = key.length();
if (line[equal_pos] == '=') {
value = line.substr(equal_pos + 1);
return true;
}
}
return false;
}
void initialize_partition()
{
// Initialize partition table
string pmbr = "/root/.pmbr_code";
// Check if pmbr exists
if (access(pmbr.c_str(), R_OK) != 0) {
error_exit("Missing " + pmbr +", please rebuild image.");
}
string device_name = get_output_device(-1);
if (system((". /usr/sbin/write_gpt.sh &&"
" write_base_table " + device_name + " " + pmbr).c_str()) != 0) {
error_exit("Cannot write partition table.");
}
cerr << "Reloading partition table changes..." << endl;
sync();
if (system(("partprobe " + device_name).c_str()) != 0) {
error_exit("Cannot reload partition table.");
}
cerr << "Make a new stateful partition." << endl;
string stateful_partition = get_output_device(kStatefulPartition);
if (system(("mkfs.ext4 " + stateful_partition).c_str()) != 0) {
error_exit("Cannot make stateful partition.");
}
cerr << "Done preparing disk." << endl;
}
string find_board()
{
const string kLsbRelease = "/etc/lsb-release";
const string kBoardString = "CHROMEOS_RELEASE_BOARD";
string buffer, board;
std::ifstream fin;
fin.open(kLsbRelease.c_str());
while(std::getline(fin, buffer)) {
if (find_key_value(buffer, kBoardString, board)) {
break;
}
}
cerr << "Board: " << board << endl;
// Can not find board, error
if (board.empty()) {
error_exit("Can not find board name, installation failed.");
}
return board;
}
// The network interface may not be ready, so let's try multiple times here
void download_config(const string& stateful_mount)
{
int retry = 10;
while (retry-- > 0) {
if (system(("wget " + kConfigUrl +
" -O " + stateful_mount + kConfigPath +
" --no-check-certificate").c_str()) == 0) {
// Succeed and retrun
return;
}
cerr << "Connection failed, wait 3 seconds and retry(" << retry <<
"times left).";
sleep(3);
}
// After 10 tries, return fail
error_exit("Fail downloading recovery image configuration.");
}
void download_image(const string& stateful_mount, const string& board)
{
std::ifstream fin;
string buffer, filename, download_link;
// To avoid the confusion of x86-alex and x86-alex-he,
// change board from board to board_
string board_ = board + "_";
fin.open((stateful_mount + kConfigPath).c_str());
while(std::getline(fin, buffer)) {
if (find_key_value(buffer, "url", filename)) {
if (filename.find(board_) != string::npos) {
download_link = filename;
break;
}
}
}
// Can not find download url, error
if (download_link.empty()) {
error_exit("Can not find download url, installation failed.");
}
// Download the release image
if (system(("wget " + download_link +
" -O " + stateful_mount + kZipImagePath +
" --no-check-certificate").c_str()) != 0) {
error_exit("Fail downloading release image.");
}
// Unzip the release image.
// To use gzip to decompress .zip file, we assume the zip archieve contain
// the release image and no other files.
// /tmp/image.bin.zip -> /tmp/image.zip
if (system(("gzip -d -f -S .zip " +
stateful_mount + kZipImagePath).c_str()) != 0) {
error_exit("Fail to unzip image.");
}
}
int get_image_offset(const int partition, const string& path)
{
// Read the offset of a partition in an image by cgpt
FILE* fp;
int offset;
stringstream command;
command << "cgpt show -b -i " << partition << " " << path;
fp = popen(command.str().c_str(), "r");
fscanf(fp, "%d", &offset);
pclose(fp);
return offset;
}
int get_image_size(const int partition, const string& path)
{
// Read the size (by number of 512 byte sectors) of a partition in an image
// by cgpt
FILE* fp;
int size;
stringstream command;
command << "cgpt show -s -i " << partition << " " << path;
fp = popen(command.str().c_str(), "r");
fscanf(fp, "%d", &size);
pclose(fp);
return size;
}
void install_partition(const string& stateful_mount,
const int partition,
const int dest=-1)
{
int sector_size = 512;
int begin_sector;
int num_sectors;
int dest_partition = (dest == -1) ? partition : dest;
string image_path = stateful_mount + kImagePath;
// Read the partition information
begin_sector = get_image_offset(partition, image_path);
num_sectors = get_image_size(partition, image_path);
// We want to enlarge sector size to speed up dd
// Increase as much as possible up to 8M
while(begin_sector % 2 == 0 && num_sectors % 2 == 0 &&
sector_size < 8 * 1024 * 1024 && num_sectors > 0) {
sector_size *= 2;
begin_sector /= 2;
num_sectors /=2 ;
}
string output_device;
output_device = get_output_device(dest_partition);
stringstream command;
// Generate dd command, pv is used to show progress.
command << "dd if=" << image_path << " bs=" << sector_size <<
" skip=" << begin_sector << " count=" << num_sectors <<
" | pv -ptreb -B 2M -s " << (uint64_t)sector_size * num_sectors <<
" | dd of=" << output_device << " bs=" << sector_size <<
" iflag=fullblock oflag=dsync";
// Install to device by dd.
cerr << command.str().c_str() << endl;
if (system(command.str().c_str()) != 0) {
error_exit("Unable to install partition.");
}
}
void install_firmware_updater()
{
// Extract firmware updater from rootfs
const string updater_path = "/usr/sbin/chromeos-firmwareupdate";
string rootfs_mount;
char tmp_name[] = "fw_XXXXXX";
string tmp_updater(mktemp(tmp_name));
rootfs_mount = mount_partition(kRootfsPartition, "-o ro -t ext2");
if (system(("cp " + rootfs_mount + updater_path +
" " + tmp_updater).c_str()) != 0) {
error_exit("Fail to copy firmware updater.");
}
unmount_partition(rootfs_mount);
// Run firmware update script
if (system(("sh " + tmp_updater +
" --force --mode=recovery").c_str()) != 0) {
error_exit("Fail to run firmware updater.");
}
if (remove(tmp_updater.c_str()) != 0) {
error_exit("Fail to remove temp updater.");
}
}
void postprocess_release(const string& stateful_mount)
{
const string kVmlinuzHdFile="vmlinuz_hd.vblock";
string image_path = stateful_mount + kImagePath;
char mount_template[] = "image_XXXXXX";
string image_mount(mkdtemp(mount_template));
image_mount += "/";
// Find of the location of stateful partition
int begin_sector = get_image_offset(kStatefulPartition, image_path);
int num_sectors = get_image_size(kStatefulPartition, image_path);
stringstream command;
command << "mount -o loop,offset=" << (uint64_t) begin_sector * 512 <<
",sizelimit=" << (uint64_t) num_sectors * 512 << ",ro" <<
" " << image_path << " " << image_mount;
cerr << command.str() << endl;
if (system(command.str().c_str()) != 0) {
error_exit("Cannot mount stateful partition from image");
}
command.str(string());
string kernel_device = get_output_device(kRealKernel);
command << "dd if=" << image_mount << kVmlinuzHdFile <<
" of=" << kernel_device << " bs=512 conv=notrunc";
if (system(command.str().c_str()) != 0) {
error_exit("Cannot update kernel with " + kVmlinuzHdFile);
}
if (system(("umount " + image_mount).c_str()) != 0) {
error_exit("Cannot umount " + image_mount);
}
if (remove(image_mount.c_str()) != 0){
error_exit("Cannot remove " + image_mount);
}
}
void install_all_partition(const string& stateful_mount)
{
install_partition(stateful_mount, kRecoveryKernel, kRealKernel);
install_partition(stateful_mount, kRootfsPartition);
install_partition(stateful_mount, kOemPartition);
install_partition(stateful_mount, kEfiPartition);
// Since we use release image, we need to copy vmlinuz_hd.block from
// stateful partition to kernel partition.
postprocess_release(stateful_mount);
}
int main()
{
string board, stateful_mount;
initialize_partition();
stateful_mount = mount_partition(kStatefulPartition);
board = find_board();
download_config(stateful_mount);
download_image(stateful_mount, board);
install_all_partition(stateful_mount);
unmount_partition(stateful_mount);
install_firmware_updater();
return 0;
}