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 |