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