blob: 0488c59ab1a38a3ed0c3600a7ce4b925a23e64e4 [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 "memento_common.h"
#include "general_installer.h"
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
using std::cerr;
using std::endl;
using std::string;
using std::stringstream;
namespace chromeos_memento_updater {
GeneralInstaller::GeneralInstaller(const string& running_dir,
const string& board) {
running_dir_ = running_dir;
board_ = board;
combined_kernel_ = false;
// Currently, we only allow partition options in image mode.
allow_partition_options_ = false;
}
GeneralInstaller::~GeneralInstaller() {}
// TODO(chunyen) : convert the system call into C++ code
bool GeneralInstaller::IsDeviceMounted(const string& device_path) {
FILE* fp;
stringstream command;
const int kMaxPathLen = 512;
char buffer[kMaxPathLen];
command << "grep \"^" << device_path <<
" \" /proc/mounts | cut -d ' ' -f 1 | uniq";
fp = popen(command.str().c_str(), "r");
if (fp == NULL) {
error_exit("Cannot check if device is mounted");
}
if (fgets(buffer, sizeof(buffer), fp) == NULL) {
fclose(fp);
return false;
}
fclose(fp);
return (device_path == buffer);
}
int GeneralInstaller::PrepareImage(const int channel_id,
FILE* out_pipe) {
return error("This method should not be called, need override");
}
int GeneralInstaller::InstallImage(const string& device_path,
const string& kernel_device_path,
FILE* in_pipe) {
stringstream command;
if (in_pipe == NULL) {
return error("Null input pipe");
}
// Unmount device when necessary
if (IsDeviceMounted(device_path)) {
if (umount(device_path.c_str()) != 0) {
return error("Cannot umount " + device_path);
}
}
command.str(string());
if (kernel_device_path.empty()) {
// TODO(chunyen) : Using dd instead of cat, or do a complete rewrite?
command << "cat > " << device_path;
} else {
command << running_dir_ << "/split_write " <<
kernel_device_path << " " << device_path;
}
FILE* out_pipe = popen(command.str().c_str(), "w");
if (out_pipe == NULL) {
return error("Fail to open device to write");
}
// Write by pipe or split write
const int kBufSize = 1024 * 1024 * 4; // 4MiB
int read_byte, write_byte;
char buf[kBufSize];
while ((read_byte = fread(buf, 1, kBufSize, in_pipe)) > 0) {
write_byte = 0;
do {
write_byte += fwrite(&buf[write_byte], 1,
read_byte - write_byte, out_pipe);
if (ferror(out_pipe)) {
ZeroOutDevice(device_path);
return error("Error writing pipe");
}
} while (write_byte < read_byte);
}
pclose(out_pipe);
if (ferror(in_pipe)) {
ZeroOutDevice(device_path);
return error("Error reading pipe");
}
// Flush linux cache.
sync();
return 0;
}
void GeneralInstaller::ResetPartitionTable(const string& device_path) {
string pmbr = "/root/.pmbr_code";
// Check if pmbr exists
if (access(pmbr.c_str(), R_OK) != 0) {
error_exit("Missing " + pmbr +", please rebuild image");
}
if (system((". /usr/sbin/write_gpt.sh &&"
" write_base_table " + device_path + " " + pmbr).c_str()) != 0) {
error_exit("Cannot write partition table.");
}
cerr << "Reloading partition table changes..." << endl;
sync();
if (system(("partprobe " + device_path).c_str()) != 0) {
error_exit("Cannot reload partition table.");
}
}
void GeneralInstaller::InitStatefulPartition(const string& device_path) {
if (system(("mkfs.ext4 " + device_path).c_str()) != 0) {
error_exit("Cannot make stateful partition.");
}
}
void GeneralInstaller::InstallDevice(const int channel_id,
const string& device_path,
const string& kernel_device_path) {
int fd[2];
pid_t pid;
if (pipe(fd) != 0) {
error_exit("Failed to create pipe");
}
pid = fork();
if (pid < 0) {
// Failed, exit.
error_exit("Fork failed");
} else if (pid == 0) {
// Child process, close fd[1] (for write), run InstallImage and _Exit().
close(fd[1]);
FILE* fp = fdopen(fd[0], "rb");
if (fp == NULL) {
perror("Fail creating file pointer for read");
_Exit(-1);
}
int return_value = InstallImage(device_path, kernel_device_path, fp);
fclose(fp);
_Exit(return_value);
} else {
// Parent process, close fd[0] (for read), run PrepareImage and wait().
close(fd[0]);
FILE* fp = fdopen(fd[1], "wb");
if (fp == NULL) {
error_exit("Fail creating file pointer for write");
}
int return_value = PrepareImage(channel_id, fp);
fclose(fp);
if (return_value != 0) {
exit(return_value);
}
if (waitpid(pid, &return_value, 0) != pid) {
error_exit("Wait pid failed");
}
if (return_value != 0) {
exit(return_value);
}
}
}
void GeneralInstaller::ZeroOutDevice(const string& device_path) {
system_call("dd if=/dev/zero of=" + device_path + " bs=4096 count=1",
"Cannnot zero out device");
}
string GeneralInstaller::PrepareFirmware(const string& rootfs_path) {
// Extract firmware updater from rootfs
MountHandler mount_handler(rootfs_path, "ext2", MS_RDONLY);
TempfileHandler temp_updater_handler(false);
const string tmp_updater = temp_updater_handler.GetFilePath();
const string updater_path = "/usr/sbin/chromeos-firmwareupdate";
const string mount_point = mount_handler.GetMountPoint();
// Here we use sendfile() to efficiently copy file without creating
// extra buffer.
int source = open((mount_point + updater_path).c_str(), O_RDONLY);
int dest = open(tmp_updater.c_str(),
O_WRONLY | O_TRUNC | O_CREAT, S_IRWXU);
struct stat stat_source;
fstat(source, &stat_source);
if (source == -1 || dest == -1) {
if (source != -1) {
close(source);
}
if (dest != -1) {
close(dest);
}
error_exit("Fail to copy firmware updater");
}
if (sendfile(dest, source, 0, stat_source.st_size) == -1) {
close(source);
close(dest);
error_exit("Fail to copy firmware updater");
}
close(source);
close(dest);
return tmp_updater;
}
void GeneralInstaller::UpdateFirmware(const string& updater_path,
const string& mode) {
stringstream command;
command << "sh " << updater_path << " --force --mode=" << mode;
system_call(command.str(), "Fail to run firmware updater");
}
void GeneralInstaller::ActivateImage(const string& device_path) {
MountHandler handler(device_path, "ext2", MS_RDONLY);
string mount_point = handler.GetMountPoint();
system_call(mount_point + "/postinst " + device_path,
"Cannot run postinst");
}
} // namespace chromeos_memento_updater