//
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "update_engine/payload_consumer/postinstall_runner_action.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <memory>
#include <string>

#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/message_loop/message_loop.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/message_loops/base_message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "update_engine/common/constants.h"
#include "update_engine/common/fake_boot_control.h"
#include "update_engine/common/fake_hardware.h"
#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"

using brillo::MessageLoop;
using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
using std::string;

namespace chromeos_update_engine {

class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
 public:
  PostinstActionProcessorDelegate() = default;
  void ProcessingDone(const ActionProcessor* processor,
                      ErrorCode code) override {
    MessageLoop::current()->BreakLoop();
    processing_done_called_ = true;
  }
  void ProcessingStopped(const ActionProcessor* processor) override {
    MessageLoop::current()->BreakLoop();
    processing_stopped_called_ = true;
  }

  void ActionCompleted(ActionProcessor* processor,
                       AbstractAction* action,
                       ErrorCode code) override {
    if (action->Type() == PostinstallRunnerAction::StaticType()) {
      code_ = code;
      code_set_ = true;
    }
  }

  ErrorCode code_{ErrorCode::kError};
  bool code_set_{false};
  bool processing_done_called_{false};
  bool processing_stopped_called_{false};
};

class MockPostinstallRunnerActionDelegate
    : public PostinstallRunnerAction::DelegateInterface {
 public:
  MOCK_METHOD1(ProgressUpdate, void(double progress));
};

class PostinstallRunnerActionTest : public ::testing::Test {
 protected:
  void SetUp() override {
    loop_.SetAsCurrent();
    async_signal_handler_.Init();
    subprocess_.Init(&async_signal_handler_);
    // These tests use the postinstall files generated by "generate_images.sh"
    // stored in the "disk_ext2_unittest.img" image.
    postinstall_image_ =
        test_utils::GetBuildArtifactsPath("gen/disk_ext2_unittest.img");
  }

  // Setup an action processor and run the PostinstallRunnerAction with a single
  // partition |device_path|, running the |postinstall_program| command from
  // there.
  void RunPosinstallAction(const string& device_path,
                           const string& postinstall_program,
                           bool powerwash_required);

 public:
  void ResumeRunningAction() {
    ASSERT_NE(nullptr, postinstall_action_);
    postinstall_action_->ResumeAction();
  }

  void SuspendRunningAction() {
    if (!postinstall_action_ || !postinstall_action_->current_command_ ||
        test_utils::Readlink(base::StringPrintf(
            "/proc/%d/fd/0", postinstall_action_->current_command_)) !=
            "/dev/zero") {
      // We need to wait for the postinstall command to start and flag that it
      // is ready by redirecting its input to /dev/zero.
      loop_.PostDelayedTask(
          FROM_HERE,
          base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
                     base::Unretained(this)),
          base::TimeDelta::FromMilliseconds(100));
    } else {
      postinstall_action_->SuspendAction();
      // Schedule to be resumed in a little bit.
      loop_.PostDelayedTask(
          FROM_HERE,
          base::Bind(&PostinstallRunnerActionTest::ResumeRunningAction,
                     base::Unretained(this)),
          base::TimeDelta::FromMilliseconds(100));
    }
  }

  void CancelWhenStarted() {
    if (!postinstall_action_ || !postinstall_action_->current_command_) {
      // Wait for the postinstall command to run.
      loop_.PostDelayedTask(
          FROM_HERE,
          base::Bind(&PostinstallRunnerActionTest::CancelWhenStarted,
                     base::Unretained(this)),
          base::TimeDelta::FromMilliseconds(10));
    } else {
      CHECK(processor_);
      processor_->StopProcessing();
    }
  }

 protected:
  base::MessageLoopForIO base_loop_;
  brillo::BaseMessageLoop loop_{&base_loop_};
  brillo::AsynchronousSignalHandler async_signal_handler_;
  Subprocess subprocess_;

  // The path to the postinstall sample image.
  string postinstall_image_;

  FakeBootControl fake_boot_control_;
  FakeHardware fake_hardware_;
  PostinstActionProcessorDelegate processor_delegate_;

  // The PostinstallRunnerAction delegate receiving the progress updates.
  PostinstallRunnerAction::DelegateInterface* setup_action_delegate_{nullptr};

  // A pointer to the posinstall_runner action and the processor.
  PostinstallRunnerAction* postinstall_action_{nullptr};
  ActionProcessor* processor_{nullptr};
};

void PostinstallRunnerActionTest::RunPosinstallAction(
    const string& device_path,
    const string& postinstall_program,
    bool powerwash_required) {
  ActionProcessor processor;
  processor_ = &processor;
  ObjectFeederAction<InstallPlan> feeder_action;
  InstallPlan::Partition part;
  part.name = "part";
  part.target_path = device_path;
  part.run_postinstall = true;
  part.postinstall_path = postinstall_program;
  InstallPlan install_plan;
  install_plan.partitions = {part};
  install_plan.download_url = "http://127.0.0.1:8080/update";
  install_plan.powerwash_required = powerwash_required;
  feeder_action.set_obj(install_plan);
  PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_);
  postinstall_action_ = &runner_action;
  runner_action.set_delegate(setup_action_delegate_);
  BondActions(&feeder_action, &runner_action);
  ObjectCollectorAction<InstallPlan> collector_action;
  BondActions(&runner_action, &collector_action);
  processor.EnqueueAction(&feeder_action);
  processor.EnqueueAction(&runner_action);
  processor.EnqueueAction(&collector_action);
  processor.set_delegate(&processor_delegate_);

  loop_.PostTask(
      FROM_HERE,
      base::Bind(
          [](ActionProcessor* processor) { processor->StartProcessing(); },
          base::Unretained(&processor)));
  loop_.Run();
  ASSERT_FALSE(processor.IsRunning());
  postinstall_action_ = nullptr;
  processor_ = nullptr;
  EXPECT_TRUE(processor_delegate_.processing_stopped_called_ ||
              processor_delegate_.processing_done_called_);
  if (processor_delegate_.processing_done_called_) {
    // Sanity check that the code was set when the processor finishes.
    EXPECT_TRUE(processor_delegate_.code_set_);
  }
}

TEST_F(PostinstallRunnerActionTest, ProcessProgressLineTest) {
  PostinstallRunnerAction action(&fake_boot_control_, &fake_hardware_);
  testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
  action.set_delegate(&mock_delegate_);

  action.current_partition_ = 1;
  action.partition_weight_ = {1, 2, 5};
  action.accumulated_weight_ = 1;
  action.total_weight_ = 8;

  // 50% of the second action is 2/8 = 0.25 of the total.
  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25));
  action.ProcessProgressLine("global_progress 0.5");
  testing::Mock::VerifyAndClearExpectations(&mock_delegate_);

  // 1.5 should be read as 100%, to catch rounding error cases like 1.000001.
  // 100% of the second is 3/8 of the total.
  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.375));
  action.ProcessProgressLine("global_progress 1.5");
  testing::Mock::VerifyAndClearExpectations(&mock_delegate_);

  // None of these should trigger a progress update.
  action.ProcessProgressLine("foo_bar");
  action.ProcessProgressLine("global_progress");
  action.ProcessProgressLine("global_progress ");
  action.ProcessProgressLine("global_progress NaN");
  action.ProcessProgressLine("global_progress Exception in ... :)");
}

// Test that postinstall succeeds in the simple case of running the default
// /postinst command which only exits 0.
TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  RunPosinstallAction(loop.dev(), kPostinstallDefaultScript, false);
  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
  EXPECT_TRUE(processor_delegate_.processing_done_called_);

  // Since powerwash_required was false, this should not trigger a powerwash.
  EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
}

TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  RunPosinstallAction(loop.dev(), "bin/postinst_link", false);
  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
}

TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  // Run a simple postinstall program but requiring a powerwash.
  RunPosinstallAction(loop.dev(), "bin/postinst_example", true);
  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);

  // Check that powerwash was scheduled.
  EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
}

// Runs postinstall from a partition file that doesn't mount, so it should
// fail.
TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
  RunPosinstallAction("/dev/null", kPostinstallDefaultScript, false);
  EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);

  // In case of failure, Postinstall should not signal a powerwash even if it
  // was requested.
  EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
}

// Check that the failures from the postinstall script cause the action to
// fail.
TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  RunPosinstallAction(loop.dev(), "bin/postinst_fail1", false);
  EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
}

// The exit code 3 and 4 are a specials cases that would be reported back to
// UMA with a different error code. Test those cases are properly detected.
TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  RunPosinstallAction(loop.dev(), "bin/postinst_fail3", false);
  EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB,
            processor_delegate_.code_);
}

// Check that you can't specify an absolute path.
TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  RunPosinstallAction(loop.dev(), "/etc/../bin/sh", false);
  EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
}

#ifdef __ANDROID__
// Check that the postinstall file is relabeled to the postinstall label.
// SElinux labels are only set on Android.
TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  RunPosinstallAction(loop.dev(), "bin/self_check_context", false);
  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
}
#endif  // __ANDROID__

// Check that you can suspend/resume postinstall actions.
TEST_F(PostinstallRunnerActionTest, RunAsRootSuspendResumeActionTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);

  // We need to wait for the child to run and setup its signal handler.
  loop_.PostTask(FROM_HERE,
                 base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
                            base::Unretained(this)));
  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
  // postinst_suspend returns 0 only if it was suspended at some point.
  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
  EXPECT_TRUE(processor_delegate_.processing_done_called_);
}

// Test that we can cancel a postinstall action while it is running.
TEST_F(PostinstallRunnerActionTest, RunAsRootCancelPostinstallActionTest) {
  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);

  // Wait for the action to start and then cancel it.
  CancelWhenStarted();
  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
  // When canceling the action, the action never finished and therefore we had
  // a ProcessingStopped call instead.
  EXPECT_FALSE(processor_delegate_.code_set_);
  EXPECT_TRUE(processor_delegate_.processing_stopped_called_);
}

// Test that we parse and process the progress reports from the progress
// file descriptor.
TEST_F(PostinstallRunnerActionTest, RunAsRootProgressUpdatesTest) {
  testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
  testing::InSequence s;
  EXPECT_CALL(mock_delegate_, ProgressUpdate(0));

  // The postinst_progress program will call with 0.25, 0.5 and 1.
  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25));
  EXPECT_CALL(mock_delegate_, ProgressUpdate(0.5));
  EXPECT_CALL(mock_delegate_, ProgressUpdate(1.));

  EXPECT_CALL(mock_delegate_, ProgressUpdate(1.));

  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
  setup_action_delegate_ = &mock_delegate_;
  RunPosinstallAction(loop.dev(), "bin/postinst_progress", false);
  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
}

}  // namespace chromeos_update_engine