1 // 2 // Copyright (C) 2011 The Android Open Source Project 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 #include "update_engine/payload_consumer/postinstall_runner_action.h" 18 19 #include <fcntl.h> 20 #include <signal.h> 21 #include <stdlib.h> 22 #include <sys/mount.h> 23 #include <sys/types.h> 24 #include <unistd.h> 25 26 #include <cmath> 27 28 #include <base/files/file_path.h> 29 #include <base/files/file_util.h> 30 #include <base/logging.h> 31 #include <base/strings/string_split.h> 32 #include <base/strings/string_util.h> 33 34 #include "update_engine/common/action_processor.h" 35 #include "update_engine/common/boot_control_interface.h" 36 #include "update_engine/common/platform_constants.h" 37 #include "update_engine/common/subprocess.h" 38 #include "update_engine/common/utils.h" 39 40 namespace { 41 42 // The file descriptor number from the postinstall program's perspective where 43 // it can report status updates. This can be any number greater than 2 (stderr), 44 // but must be kept in sync with the "bin/postinst_progress" defined in the 45 // sample_images.sh file. 46 const int kPostinstallStatusFd = 3; 47 48 } // namespace 49 50 namespace chromeos_update_engine { 51 52 using brillo::MessageLoop; 53 using std::string; 54 using std::vector; 55 56 void PostinstallRunnerAction::PerformAction() { 57 CHECK(HasInputObject()); 58 install_plan_ = GetInputObject(); 59 60 // Currently we're always powerwashing when rolling back. 61 if (install_plan_.powerwash_required || install_plan_.is_rollback) { 62 if (hardware_->SchedulePowerwash(install_plan_.is_rollback)) { 63 powerwash_scheduled_ = true; 64 } else { 65 return CompletePostinstall(ErrorCode::kPostinstallPowerwashError); 66 } 67 } 68 69 // Initialize all the partition weights. 70 partition_weight_.resize(install_plan_.partitions.size()); 71 total_weight_ = 0; 72 for (size_t i = 0; i < install_plan_.partitions.size(); ++i) { 73 auto& partition = install_plan_.partitions[i]; 74 if (!install_plan_.run_post_install && partition.postinstall_optional) { 75 partition.run_postinstall = false; 76 LOG(INFO) << "Skipping optional post-install for partition " 77 << partition.name << " according to install plan."; 78 } 79 80 // TODO(deymo): This code sets the weight to all the postinstall commands, 81 // but we could remember how long they took in the past and use those 82 // values. 83 partition_weight_[i] = partition.run_postinstall; 84 total_weight_ += partition_weight_[i]; 85 } 86 accumulated_weight_ = 0; 87 ReportProgress(0); 88 89 PerformPartitionPostinstall(); 90 } 91 92 void PostinstallRunnerAction::PerformPartitionPostinstall() { 93 if (install_plan_.download_url.empty()) { 94 LOG(INFO) << "Skipping post-install during rollback"; 95 return CompletePostinstall(ErrorCode::kSuccess); 96 } 97 98 // Skip all the partitions that don't have a post-install step. 99 while (current_partition_ < install_plan_.partitions.size() && 100 !install_plan_.partitions[current_partition_].run_postinstall) { 101 VLOG(1) << "Skipping post-install on partition " 102 << install_plan_.partitions[current_partition_].name; 103 current_partition_++; 104 } 105 if (current_partition_ == install_plan_.partitions.size()) 106 return CompletePostinstall(ErrorCode::kSuccess); 107 108 const InstallPlan::Partition& partition = 109 install_plan_.partitions[current_partition_]; 110 111 const string mountable_device = 112 utils::MakePartitionNameForMount(partition.target_path); 113 if (mountable_device.empty()) { 114 LOG(ERROR) << "Cannot make mountable device from " << partition.target_path; 115 return CompletePostinstall(ErrorCode::kPostinstallRunnerError); 116 } 117 118 // Perform post-install for the current_partition_ partition. At this point we 119 // need to call CompletePartitionPostinstall to complete the operation and 120 // cleanup. 121 #ifdef __ANDROID__ 122 fs_mount_dir_ = "/postinstall"; 123 #else // __ANDROID__ 124 base::FilePath temp_dir; 125 TEST_AND_RETURN(base::CreateNewTempDirectory("au_postint_mount", &temp_dir)); 126 fs_mount_dir_ = temp_dir.value(); 127 #endif // __ANDROID__ 128 129 // Double check that the fs_mount_dir is not busy with a previous mounted 130 // filesystem from a previous crashed postinstall step. 131 if (utils::IsMountpoint(fs_mount_dir_)) { 132 LOG(INFO) << "Found previously mounted filesystem at " << fs_mount_dir_; 133 utils::UnmountFilesystem(fs_mount_dir_); 134 } 135 136 base::FilePath postinstall_path(partition.postinstall_path); 137 if (postinstall_path.IsAbsolute()) { 138 LOG(ERROR) << "Invalid absolute path passed to postinstall, use a relative" 139 "path instead: " 140 << partition.postinstall_path; 141 return CompletePostinstall(ErrorCode::kPostinstallRunnerError); 142 } 143 144 string abs_path = 145 base::FilePath(fs_mount_dir_).Append(postinstall_path).value(); 146 if (!base::StartsWith( 147 abs_path, fs_mount_dir_, base::CompareCase::SENSITIVE)) { 148 LOG(ERROR) << "Invalid relative postinstall path: " 149 << partition.postinstall_path; 150 return CompletePostinstall(ErrorCode::kPostinstallRunnerError); 151 } 152 153 #ifdef __ANDROID__ 154 // In Chromium OS, the postinstall step is allowed to write to the block 155 // device on the target image, so we don't mark it as read-only and should 156 // be read-write since we just wrote to it during the update. 157 158 // Mark the block device as read-only before mounting for post-install. 159 if (!utils::SetBlockDeviceReadOnly(mountable_device, true)) { 160 return CompletePartitionPostinstall( 161 1, "Error marking the device " + mountable_device + " read only."); 162 } 163 #endif // __ANDROID__ 164 165 if (!utils::MountFilesystem(mountable_device, 166 fs_mount_dir_, 167 MS_RDONLY, 168 partition.filesystem_type, 169 constants::kPostinstallMountOptions)) { 170 return CompletePartitionPostinstall( 171 1, "Error mounting the device " + mountable_device); 172 } 173 174 LOG(INFO) << "Performing postinst (" << partition.postinstall_path << " at " 175 << abs_path << ") installed on device " << partition.target_path 176 << " and mountable device " << mountable_device; 177 178 // Logs the file format of the postinstall script we are about to run. This 179 // will help debug when the postinstall script doesn't match the architecture 180 // of our build. 181 LOG(INFO) << "Format file for new " << partition.postinstall_path 182 << " is: " << utils::GetFileFormat(abs_path); 183 184 // Runs the postinstall script asynchronously to free up the main loop while 185 // it's running. 186 vector<string> command = {abs_path}; 187 #ifdef __ANDROID__ 188 // In Brillo and Android, we pass the slot number and status fd. 189 command.push_back(std::to_string(install_plan_.target_slot)); 190 command.push_back(std::to_string(kPostinstallStatusFd)); 191 #else 192 // Chrome OS postinstall expects the target rootfs as the first parameter. 193 command.push_back(partition.target_path); 194 #endif // __ANDROID__ 195 196 current_command_ = Subprocess::Get().ExecFlags( 197 command, 198 Subprocess::kRedirectStderrToStdout, 199 {kPostinstallStatusFd}, 200 base::Bind(&PostinstallRunnerAction::CompletePartitionPostinstall, 201 base::Unretained(this))); 202 // Subprocess::Exec should never return a negative process id. 203 CHECK_GE(current_command_, 0); 204 205 if (!current_command_) { 206 CompletePartitionPostinstall(1, "Postinstall didn't launch"); 207 return; 208 } 209 210 // Monitor the status file descriptor. 211 progress_fd_ = 212 Subprocess::Get().GetPipeFd(current_command_, kPostinstallStatusFd); 213 int fd_flags = fcntl(progress_fd_, F_GETFL, 0) | O_NONBLOCK; 214 if (HANDLE_EINTR(fcntl(progress_fd_, F_SETFL, fd_flags)) < 0) { 215 PLOG(ERROR) << "Unable to set non-blocking I/O mode on fd " << progress_fd_; 216 } 217 218 progress_task_ = MessageLoop::current()->WatchFileDescriptor( 219 FROM_HERE, 220 progress_fd_, 221 MessageLoop::WatchMode::kWatchRead, 222 true, 223 base::Bind(&PostinstallRunnerAction::OnProgressFdReady, 224 base::Unretained(this))); 225 } 226 227 void PostinstallRunnerAction::OnProgressFdReady() { 228 char buf[1024]; 229 size_t bytes_read; 230 do { 231 bytes_read = 0; 232 bool eof; 233 bool ok = 234 utils::ReadAll(progress_fd_, buf, arraysize(buf), &bytes_read, &eof); 235 progress_buffer_.append(buf, bytes_read); 236 // Process every line. 237 vector<string> lines = base::SplitString( 238 progress_buffer_, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); 239 if (!lines.empty()) { 240 progress_buffer_ = lines.back(); 241 lines.pop_back(); 242 for (const auto& line : lines) { 243 ProcessProgressLine(line); 244 } 245 } 246 if (!ok || eof) { 247 // There was either an error or an EOF condition, so we are done watching 248 // the file descriptor. 249 MessageLoop::current()->CancelTask(progress_task_); 250 progress_task_ = MessageLoop::kTaskIdNull; 251 return; 252 } 253 } while (bytes_read); 254 } 255 256 bool PostinstallRunnerAction::ProcessProgressLine(const string& line) { 257 double frac = 0; 258 if (sscanf(line.c_str(), "global_progress %lf", &frac) == 1 && 259 !std::isnan(frac)) { 260 ReportProgress(frac); 261 return true; 262 } 263 264 return false; 265 } 266 267 void PostinstallRunnerAction::ReportProgress(double frac) { 268 if (!delegate_) 269 return; 270 if (current_partition_ >= partition_weight_.size() || total_weight_ == 0) { 271 delegate_->ProgressUpdate(1.); 272 return; 273 } 274 if (!std::isfinite(frac) || frac < 0) 275 frac = 0; 276 if (frac > 1) 277 frac = 1; 278 double postinst_action_progress = 279 (accumulated_weight_ + partition_weight_[current_partition_] * frac) / 280 total_weight_; 281 delegate_->ProgressUpdate(postinst_action_progress); 282 } 283 284 void PostinstallRunnerAction::Cleanup() { 285 utils::UnmountFilesystem(fs_mount_dir_); 286 #ifndef __ANDROID__ 287 if (!base::DeleteFile(base::FilePath(fs_mount_dir_), false)) { 288 PLOG(WARNING) << "Not removing temporary mountpoint " << fs_mount_dir_; 289 } 290 #endif // !__ANDROID__ 291 fs_mount_dir_.clear(); 292 293 progress_fd_ = -1; 294 if (progress_task_ != MessageLoop::kTaskIdNull) { 295 MessageLoop::current()->CancelTask(progress_task_); 296 progress_task_ = MessageLoop::kTaskIdNull; 297 } 298 progress_buffer_.clear(); 299 } 300 301 void PostinstallRunnerAction::CompletePartitionPostinstall( 302 int return_code, const string& output) { 303 current_command_ = 0; 304 Cleanup(); 305 306 if (return_code != 0) { 307 LOG(ERROR) << "Postinst command failed with code: " << return_code; 308 ErrorCode error_code = ErrorCode::kPostinstallRunnerError; 309 310 if (return_code == 3) { 311 // This special return code means that we tried to update firmware, 312 // but couldn't because we booted from FW B, and we need to reboot 313 // to get back to FW A. 314 error_code = ErrorCode::kPostinstallBootedFromFirmwareB; 315 } 316 317 if (return_code == 4) { 318 // This special return code means that we tried to update firmware, 319 // but couldn't because we booted from FW B, and we need to reboot 320 // to get back to FW A. 321 error_code = ErrorCode::kPostinstallFirmwareRONotUpdatable; 322 } 323 324 // If postinstall script for this partition is optional we can ignore the 325 // result. 326 if (install_plan_.partitions[current_partition_].postinstall_optional) { 327 LOG(INFO) << "Ignoring postinstall failure since it is optional"; 328 } else { 329 return CompletePostinstall(error_code); 330 } 331 } 332 accumulated_weight_ += partition_weight_[current_partition_]; 333 current_partition_++; 334 ReportProgress(0); 335 336 PerformPartitionPostinstall(); 337 } 338 339 void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) { 340 // We only attempt to mark the new slot as active if all the postinstall 341 // steps succeeded. 342 if (error_code == ErrorCode::kSuccess) { 343 if (install_plan_.switch_slot_on_reboot) { 344 if (!boot_control_->GetDynamicPartitionControl()->FinishUpdate( 345 install_plan_.powerwash_required) || 346 !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) { 347 error_code = ErrorCode::kPostinstallRunnerError; 348 } else { 349 // Schedules warm reset on next reboot, ignores the error. 350 hardware_->SetWarmReset(true); 351 } 352 } else { 353 error_code = ErrorCode::kUpdatedButNotActive; 354 } 355 } 356 357 ScopedActionCompleter completer(processor_, this); 358 completer.set_code(error_code); 359 360 if (error_code != ErrorCode::kSuccess && 361 error_code != ErrorCode::kUpdatedButNotActive) { 362 LOG(ERROR) << "Postinstall action failed."; 363 364 // Undo any changes done to trigger Powerwash. 365 if (powerwash_scheduled_) 366 hardware_->CancelPowerwash(); 367 368 return; 369 } 370 371 LOG(INFO) << "All post-install commands succeeded"; 372 if (HasOutputPipe()) { 373 SetOutputObject(install_plan_); 374 } 375 } 376 377 void PostinstallRunnerAction::SuspendAction() { 378 if (!current_command_) 379 return; 380 if (kill(current_command_, SIGSTOP) != 0) { 381 PLOG(ERROR) << "Couldn't pause child process " << current_command_; 382 } else { 383 is_current_command_suspended_ = true; 384 } 385 } 386 387 void PostinstallRunnerAction::ResumeAction() { 388 if (!current_command_) 389 return; 390 if (kill(current_command_, SIGCONT) != 0) { 391 PLOG(ERROR) << "Couldn't resume child process " << current_command_; 392 } else { 393 is_current_command_suspended_ = false; 394 } 395 } 396 397 void PostinstallRunnerAction::TerminateProcessing() { 398 if (!current_command_) 399 return; 400 // Calling KillExec() will discard the callback we registered and therefore 401 // the unretained reference to this object. 402 Subprocess::Get().KillExec(current_command_); 403 404 // If the command has been suspended, resume it after KillExec() so that the 405 // process can process the SIGTERM sent by KillExec(). 406 if (is_current_command_suspended_) { 407 ResumeAction(); 408 } 409 410 current_command_ = 0; 411 Cleanup(); 412 } 413 414 } // namespace chromeos_update_engine 415