blob: 9c644df8ef93350581f69bb1243007c33200d23b [file] [log] [blame]
// Copyright (c) 2009 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 "update_engine/install_action.h"
#include <errno.h>
#include <vector>
#include <gflags/gflags.h>
#include "update_engine/filesystem_iterator.h"
#include "update_engine/gzip.h"
#include "update_engine/subprocess.h"
#include "update_engine/utils.h"
DEFINE_string(mount_install_path, "",
"If set, the path to use when mounting the "
"destination device during install");
using std::vector;
namespace chromeos_update_engine {
namespace {
const string kBspatchPath = "/usr/bin/bspatch";
}
void InstallAction::PerformAction() {
ScopedActionCompleter completer(processor_, this);
// For now, do nothing other than pass what we need to to the output pipe
CHECK(HasInputObject());
const InstallPlan install_plan = GetInputObject();
if (HasOutputPipe())
SetOutputObject(install_plan.install_path);
if (install_plan.is_full_update) {
// No need to perform an install
completer.set_success(true);
return;
}
// We have a delta update.
// Open delta file
DeltaDiffParser parser(install_plan.download_path);
if (!parser.valid()) {
LOG(ERROR) << "Unable to open delta file";
return;
}
// Mount install fs
string mountpoint = FLAGS_mount_install_path;
if (mountpoint.empty()) {
// Set up dest_path_
char *mountpoint_temp = strdup("/tmp/install_mnt.XXXXXX");
CHECK(mountpoint_temp);
CHECK_EQ(mountpoint_temp, mkdtemp(mountpoint_temp));
CHECK_NE('\0', mountpoint_temp[0]);
mountpoint = mountpoint_temp;
free(mountpoint_temp);
}
TEST_AND_RETURN(utils::MountFilesystem(install_plan.install_path,
mountpoint));
// Automatically unmount the fs when this goes out of scope:
ScopedFilesystemUnmounter filesystem_unmounter(mountpoint);
{
// iterate through existing fs, deleting unneeded files
FilesystemIterator iter(mountpoint,
utils::SetWithValue<string>("/lost+found"));
for (; !iter.IsEnd(); iter.Increment()) {
if (!parser.ContainsPath(iter.GetPartialPath())) {
VLOG(1) << "install removing local path: " << iter.GetFullPath();
TEST_AND_RETURN(utils::RecursiveUnlinkDir(iter.GetFullPath()));
}
}
TEST_AND_RETURN(!iter.IsErr());
}
// iterate through delta metadata, writing files
DeltaDiffParserIterator iter = parser.Begin();
for (; iter != parser.End(); iter.Increment()) {
const DeltaArchiveManifest_File& file = iter.GetFile();
VLOG(1) << "Installing file: " << iter.path();
TEST_AND_RETURN(InstallFile(mountpoint, file, iter.path(), parser));
}
completer.set_success(true);
}
bool InstallAction::InstallFile(const std::string& mountpoint,
const DeltaArchiveManifest_File& file,
const std::string& path,
const DeltaDiffParser& parser) const {
// See what's already there
struct stat existing_stbuf;
int result = lstat((mountpoint + path).c_str(), &existing_stbuf);
TEST_AND_RETURN_FALSE_ERRNO((result == 0) || (errno == ENOENT));
bool exists = (result == 0);
// Create the proper file
if (S_ISDIR(file.mode())) {
if (!exists) {
TEST_AND_RETURN_FALSE_ERRNO(
(mkdir((mountpoint + path).c_str(), file.mode())) == 0);
}
} else if (S_ISLNK(file.mode())) {
InstallFileSymlink(mountpoint, file, path, parser, exists);
} else if (S_ISCHR(file.mode()) ||
S_ISBLK(file.mode()) ||
S_ISFIFO(file.mode()) ||
S_ISSOCK(file.mode())) {
InstallFileSpecialFile(mountpoint, file, path, parser, exists);
} else if (S_ISREG(file.mode())) {
InstallFileRegularFile(mountpoint, file, path, parser, exists);
} else {
// unknown mode type
TEST_AND_RETURN_FALSE(false);
}
// chmod/chown new file
if (!S_ISLNK(file.mode()))
TEST_AND_RETURN_FALSE_ERRNO(chmod((mountpoint + path).c_str(), file.mode())
== 0);
TEST_AND_RETURN_FALSE(file.has_uid() && file.has_gid());
TEST_AND_RETURN_FALSE_ERRNO(lchown((mountpoint + path).c_str(),
file.uid(), file.gid()) == 0);
return true;
}
bool InstallAction::InstallFileRegularFile(
const std::string& mountpoint,
const DeltaArchiveManifest_File& file,
const std::string& path,
const DeltaDiffParser& parser,
const bool exists) const {
if (!file.has_data_format())
return true;
TEST_AND_RETURN_FALSE(file.has_data_offset() && file.has_data_length());
if (file.data_format() == DeltaArchiveManifest_File_DataFormat_BSDIFF) {
// Expand with bspatch
string patch_path = utils::TempFilename(mountpoint + path + ".XXXXXX");
TEST_AND_RETURN_FALSE(file.has_data_length());
TEST_AND_RETURN_FALSE(parser.CopyDataToFile(
file.data_offset(),
static_cast<off_t>(file.data_length()), false,
patch_path));
string output_path = utils::TempFilename(mountpoint + path + ".XXXXXX");
int rc = 1;
vector<string> cmd;
cmd.push_back(kBspatchPath);
cmd.push_back(mountpoint + path);
cmd.push_back(output_path);
cmd.push_back(patch_path);
TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc));
TEST_AND_RETURN_FALSE(rc == 0);
TEST_AND_RETURN_FALSE_ERRNO(rename(output_path.c_str(),
(mountpoint + path).c_str()) == 0);
TEST_AND_RETURN_FALSE_ERRNO(unlink(patch_path.c_str()) == 0);
} else {
// Expand full data, decompressing if necessary
TEST_AND_RETURN_FALSE((file.data_format() ==
DeltaArchiveManifest_File_DataFormat_FULL) ||
(file.data_format() ==
DeltaArchiveManifest_File_DataFormat_FULL_GZ));
if (exists)
TEST_AND_RETURN_FALSE_ERRNO(unlink((mountpoint + path).c_str()) == 0);
TEST_AND_RETURN_FALSE(file.has_data_length());
const bool gzipped = file.data_format() ==
DeltaArchiveManifest_File_DataFormat_FULL_GZ;
bool success =
parser.CopyDataToFile(file.data_offset(), file.data_length(),
gzipped,
mountpoint + path);
TEST_AND_RETURN_FALSE(success);
}
return true;
}
// char/block devices, fifos, and sockets:
bool InstallAction::InstallFileSpecialFile(
const std::string& mountpoint,
const DeltaArchiveManifest_File& file,
const std::string& path,
const DeltaDiffParser& parser,
const bool exists) const {
if (exists)
TEST_AND_RETURN_FALSE(unlink((mountpoint + path).c_str()) == 0);
dev_t dev = 0;
if (S_ISCHR(file.mode()) || S_ISBLK(file.mode())) {
vector<char> dev_proto;
TEST_AND_RETURN_FALSE(parser.ReadDataVector(file.data_offset(),
file.data_length(),
&dev_proto));
if (file.data_format() == DeltaArchiveManifest_File_DataFormat_FULL_GZ) {
TEST_AND_RETURN_FALSE(file.has_data_length());
{
vector<char> decompressed_dev_proto;
TEST_AND_RETURN_FALSE(GzipDecompress(dev_proto,
&decompressed_dev_proto));
dev_proto = decompressed_dev_proto;
}
} else {
TEST_AND_RETURN_FALSE(file.data_format() ==
DeltaArchiveManifest_File_DataFormat_FULL);
}
LinuxDevice linux_device;
utils::HexDumpVector(dev_proto);
TEST_AND_RETURN_FALSE(linux_device.ParseFromArray(&dev_proto[0],
dev_proto.size()));
dev = makedev(linux_device.major(), linux_device.minor());
}
TEST_AND_RETURN_FALSE_ERRNO(mknod((mountpoint + path).c_str(),
file.mode(), dev) == 0);
return true;
}
// symlinks:
bool InstallAction::InstallFileSymlink(const std::string& mountpoint,
const DeltaArchiveManifest_File& file,
const std::string& path,
const DeltaDiffParser& parser,
const bool exists) const {
// If there's no data, we leave the symlink as is
if (!file.has_data_format())
return true; // No changes needed
TEST_AND_RETURN_FALSE((file.data_format() ==
DeltaArchiveManifest_File_DataFormat_FULL) ||
(file.data_format() ==
DeltaArchiveManifest_File_DataFormat_FULL_GZ));
TEST_AND_RETURN_FALSE(file.has_data_offset() && file.has_data_length());
// We have data, and thus use it to create a symlink.
// First delete any existing symlink:
if (exists)
TEST_AND_RETURN_FALSE_ERRNO(unlink((mountpoint + path).c_str()) == 0);
vector<char> symlink_data;
TEST_AND_RETURN_FALSE(parser.ReadDataVector(file.data_offset(),
file.data_length(),
&symlink_data));
if (file.data_format() == DeltaArchiveManifest_File_DataFormat_FULL_GZ) {
vector<char> decompressed_symlink_data;
TEST_AND_RETURN_FALSE(GzipDecompress(symlink_data,
&decompressed_symlink_data));
symlink_data = decompressed_symlink_data;
}
symlink_data.push_back('\0');
TEST_AND_RETURN_FALSE_ERRNO(symlink(&symlink_data[0],
(mountpoint + path).c_str()) == 0);
return true;
}
} // namespace chromeos_update_engine