Allie Wood | eb9e6d8 | 2015-04-17 13:55:30 -0700 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "update_engine/filesystem_verifier_action.h" |
| 6 | |
| 7 | #include <errno.h> |
| 8 | #include <fcntl.h> |
| 9 | #include <sys/stat.h> |
| 10 | #include <sys/types.h> |
| 11 | |
| 12 | #include <algorithm> |
| 13 | #include <cstdlib> |
| 14 | #include <string> |
| 15 | |
| 16 | #include <gio/gio.h> |
| 17 | #include <gio/gunixinputstream.h> |
| 18 | #include <glib.h> |
| 19 | |
| 20 | #include "update_engine/glib_utils.h" |
| 21 | #include "update_engine/hardware_interface.h" |
| 22 | #include "update_engine/subprocess.h" |
| 23 | #include "update_engine/system_state.h" |
| 24 | #include "update_engine/utils.h" |
| 25 | |
| 26 | using std::string; |
| 27 | |
| 28 | namespace chromeos_update_engine { |
| 29 | |
| 30 | namespace { |
| 31 | const off_t kCopyFileBufferSize = 128 * 1024; |
| 32 | } // namespace |
| 33 | |
| 34 | FilesystemVerifierAction::FilesystemVerifierAction( |
| 35 | SystemState* system_state, |
| 36 | PartitionType partition_type) |
| 37 | : partition_type_(partition_type), |
| 38 | src_stream_(nullptr), |
| 39 | canceller_(nullptr), |
| 40 | read_done_(false), |
| 41 | failed_(false), |
| 42 | cancelled_(false), |
| 43 | remaining_size_(kint64max), |
| 44 | system_state_(system_state) {} |
| 45 | |
| 46 | void FilesystemVerifierAction::PerformAction() { |
| 47 | // Will tell the ActionProcessor we've failed if we return. |
| 48 | ScopedActionCompleter abort_action_completer(processor_, this); |
| 49 | |
| 50 | if (!HasInputObject()) { |
| 51 | LOG(ERROR) << "FilesystemVerifierAction missing input object."; |
| 52 | return; |
| 53 | } |
| 54 | install_plan_ = GetInputObject(); |
| 55 | |
| 56 | if (partition_type_ == PartitionType::kKernel) { |
| 57 | LOG(INFO) << "verifying kernel, marking as unbootable"; |
| 58 | if (!system_state_->hardware()->MarkKernelUnbootable( |
| 59 | install_plan_.kernel_install_path)) { |
| 60 | PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " << |
| 61 | install_plan_.kernel_install_path; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | if (install_plan_.is_full_update && |
| 66 | (partition_type_ == PartitionType::kSourceRootfs || |
| 67 | partition_type_ == PartitionType::kSourceKernel)) { |
| 68 | // No hash verification needed. Done! |
| 69 | LOG(INFO) << "filesystem verifying skipped on full update."; |
| 70 | if (HasOutputPipe()) |
| 71 | SetOutputObject(install_plan_); |
| 72 | abort_action_completer.set_code(ErrorCode::kSuccess); |
| 73 | return; |
| 74 | } |
| 75 | |
| 76 | string target_path; |
| 77 | switch (partition_type_) { |
| 78 | case PartitionType::kRootfs: |
| 79 | target_path = install_plan_.install_path; |
| 80 | if (target_path.empty()) { |
| 81 | utils::GetInstallDev(system_state_->hardware()->BootDevice(), |
| 82 | &target_path); |
| 83 | } |
| 84 | break; |
| 85 | case PartitionType::kKernel: |
| 86 | target_path = install_plan_.kernel_install_path; |
| 87 | if (target_path.empty()) { |
| 88 | string rootfs_path; |
| 89 | utils::GetInstallDev(system_state_->hardware()->BootDevice(), |
| 90 | &rootfs_path); |
| 91 | target_path = utils::KernelDeviceOfBootDevice(rootfs_path); |
| 92 | } |
| 93 | break; |
| 94 | case PartitionType::kSourceRootfs: |
| 95 | target_path = install_plan_.source_path.empty() ? |
| 96 | system_state_->hardware()->BootDevice() : |
| 97 | install_plan_.source_path; |
| 98 | break; |
| 99 | case PartitionType::kSourceKernel: |
| 100 | target_path = install_plan_.kernel_source_path.empty() ? |
| 101 | utils::KernelDeviceOfBootDevice( |
| 102 | system_state_->hardware()->BootDevice()) : |
| 103 | install_plan_.kernel_source_path; |
| 104 | break; |
| 105 | } |
| 106 | |
| 107 | int src_fd = open(target_path.c_str(), O_RDONLY); |
| 108 | if (src_fd < 0) { |
| 109 | PLOG(ERROR) << "Unable to open " << target_path << " for reading:"; |
| 110 | return; |
| 111 | } |
| 112 | |
| 113 | DetermineFilesystemSize(src_fd); |
| 114 | src_stream_ = g_unix_input_stream_new(src_fd, TRUE); |
| 115 | |
| 116 | buffer_.resize(kCopyFileBufferSize); |
| 117 | canceller_ = g_cancellable_new(); |
| 118 | |
| 119 | // Start the first read. |
| 120 | SpawnAsyncActions(); |
| 121 | |
| 122 | abort_action_completer.set_should_complete(false); |
| 123 | } |
| 124 | |
| 125 | void FilesystemVerifierAction::TerminateProcessing() { |
| 126 | if (canceller_) { |
| 127 | g_cancellable_cancel(canceller_); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | bool FilesystemVerifierAction::IsCleanupPending() const { |
| 132 | return (src_stream_ != nullptr); |
| 133 | } |
| 134 | |
| 135 | void FilesystemVerifierAction::Cleanup(ErrorCode code) { |
| 136 | g_object_unref(canceller_); |
| 137 | canceller_ = nullptr; |
| 138 | g_object_unref(src_stream_); |
| 139 | src_stream_ = nullptr; |
| 140 | if (cancelled_) |
| 141 | return; |
| 142 | if (code == ErrorCode::kSuccess && HasOutputPipe()) |
| 143 | SetOutputObject(install_plan_); |
| 144 | processor_->ActionComplete(this, code); |
| 145 | } |
| 146 | |
| 147 | void FilesystemVerifierAction::AsyncReadReadyCallback(GObject *source_object, |
| 148 | GAsyncResult *res) { |
| 149 | GError* error = nullptr; |
| 150 | CHECK(canceller_); |
| 151 | cancelled_ = g_cancellable_is_cancelled(canceller_) == TRUE; |
| 152 | |
| 153 | ssize_t bytes_read = g_input_stream_read_finish(src_stream_, res, &error); |
| 154 | |
| 155 | if (bytes_read < 0) { |
| 156 | LOG(ERROR) << "Read failed: " << utils::GetAndFreeGError(&error); |
| 157 | failed_ = true; |
| 158 | } else if (bytes_read == 0) { |
| 159 | read_done_ = true; |
| 160 | } else { |
| 161 | remaining_size_ -= bytes_read; |
| 162 | } |
| 163 | |
| 164 | if (bytes_read > 0) { |
| 165 | // If read_done_ is set, SpawnAsyncActions may finalize the hash so the hash |
| 166 | // update below would happen too late. |
| 167 | CHECK(!read_done_); |
| 168 | if (!hasher_.Update(buffer_.data(), bytes_read)) { |
| 169 | LOG(ERROR) << "Unable to update the hash."; |
| 170 | failed_ = true; |
| 171 | } |
| 172 | } |
| 173 | SpawnAsyncActions(); |
| 174 | } |
| 175 | |
| 176 | void FilesystemVerifierAction::StaticAsyncReadReadyCallback( |
| 177 | GObject *source_object, |
| 178 | GAsyncResult *res, |
| 179 | gpointer user_data) { |
| 180 | reinterpret_cast<FilesystemVerifierAction*>(user_data)-> |
| 181 | AsyncReadReadyCallback(source_object, res); |
| 182 | } |
| 183 | |
| 184 | void FilesystemVerifierAction::SpawnAsyncActions() { |
| 185 | if (failed_ || cancelled_) { |
| 186 | Cleanup(ErrorCode::kError); |
| 187 | return; |
| 188 | } |
| 189 | |
| 190 | if (!read_done_) { |
| 191 | int64_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()), |
| 192 | remaining_size_); |
| 193 | g_input_stream_read_async( |
| 194 | src_stream_, |
| 195 | buffer_.data(), |
| 196 | bytes_to_read, |
| 197 | G_PRIORITY_DEFAULT, |
| 198 | canceller_, |
| 199 | &FilesystemVerifierAction::StaticAsyncReadReadyCallback, |
| 200 | this); |
| 201 | } else { |
| 202 | // We're done! |
| 203 | ErrorCode code = ErrorCode::kSuccess; |
| 204 | if (hasher_.Finalize()) { |
| 205 | LOG(INFO) << "Hash: " << hasher_.hash(); |
| 206 | switch (partition_type_) { |
| 207 | case PartitionType::kRootfs: |
| 208 | if (install_plan_.rootfs_hash != hasher_.raw_hash()) { |
| 209 | code = ErrorCode::kNewRootfsVerificationError; |
| 210 | LOG(ERROR) << "New rootfs verification failed."; |
| 211 | } |
| 212 | break; |
| 213 | case PartitionType::kKernel: |
| 214 | if (install_plan_.kernel_hash != hasher_.raw_hash()) { |
| 215 | code = ErrorCode::kNewKernelVerificationError; |
| 216 | LOG(ERROR) << "New kernel verification failed."; |
| 217 | } |
| 218 | break; |
| 219 | case PartitionType::kSourceRootfs: |
| 220 | install_plan_.source_rootfs_hash = hasher_.raw_hash(); |
| 221 | break; |
| 222 | case PartitionType::kSourceKernel: |
| 223 | install_plan_.source_kernel_hash = hasher_.raw_hash(); |
| 224 | break; |
| 225 | } |
| 226 | } else { |
| 227 | LOG(ERROR) << "Unable to finalize the hash."; |
| 228 | code = ErrorCode::kError; |
| 229 | } |
| 230 | Cleanup(code); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | void FilesystemVerifierAction::DetermineFilesystemSize(int fd) { |
| 235 | switch (partition_type_) { |
| 236 | case PartitionType::kRootfs: |
| 237 | remaining_size_ = install_plan_.rootfs_size; |
| 238 | LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes."; |
| 239 | break; |
| 240 | case PartitionType::kKernel: |
| 241 | remaining_size_ = install_plan_.kernel_size; |
| 242 | LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes."; |
| 243 | break; |
| 244 | case PartitionType::kSourceRootfs: |
| 245 | { |
| 246 | int block_count = 0, block_size = 0; |
| 247 | if (utils::GetFilesystemSizeFromFD(fd, &block_count, &block_size)) { |
| 248 | remaining_size_ = static_cast<int64_t>(block_count) * block_size; |
| 249 | LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes (" |
| 250 | << block_count << "x" << block_size << ")."; |
| 251 | } |
| 252 | } |
| 253 | break; |
| 254 | default: |
| 255 | break; |
| 256 | } |
| 257 | return; |
| 258 | } |
| 259 | |
| 260 | } // namespace chromeos_update_engine |