| Index: src/platform/update_engine/install_action.cc
 | 
| diff --git a/src/platform/update_engine/install_action.cc b/src/platform/update_engine/install_action.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..9c644df8ef93350581f69bb1243007c33200d23b
 | 
| --- /dev/null
 | 
| +++ b/src/platform/update_engine/install_action.cc
 | 
| @@ -0,0 +1,249 @@
 | 
| +// 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
 | 
| 
 |