| // 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 |