/*
 * Copyright (C) 2018 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.
 */

#define LOG_TAG "apexd"

#include <android-base/logging.h>
#include <android-base/properties.h>
#include <selinux/android.h>
#include <strings.h>
#include <sys/stat.h>

#include <memory>

#include "apex_file_repository.h"
#include "apexd.h"
#include "apexd_checkpoint_vold.h"
#include "apexd_lifecycle.h"
#include "apexd_metrics_stats.h"
#include "apexservice.h"
#include "com_android_apex_flags.h"

namespace flags = com::android::apex::flags;

namespace {

using android::base::SetDefaultTag;

int HandleSubcommand(int argc, char** argv) {
  if (strcmp("--bootstrap", argv[1]) == 0) {
    SetDefaultTag("apexd-bootstrap");
    return android::apex::OnBootstrap();
  }

  if (strcmp("--unmount-all", argv[1]) == 0) {
    SetDefaultTag("apexd-unmount-all");
    bool also_include_staged_apexes =
        argc >= 3 && strcmp("--also-include-staged-apexes", argv[2]) == 0;
    std::unique_ptr<android::apex::ApexSessionManager> session_manager;
    if (also_include_staged_apexes) {
      session_manager = android::apex::ApexSessionManager::Create(
          android::apex::GetSessionsDir());
      android::apex::InitializeSessionManager(session_manager.get());
    }
    return android::apex::UnmountAll(also_include_staged_apexes);
  }

  if (strcmp("--otachroot-bootstrap", argv[1]) == 0) {
    SetDefaultTag("apexd-otachroot");
    bool also_include_staged_apexes =
        argc >= 3 && strcmp("--also-include-staged-apexes", argv[2]) == 0;
    std::unique_ptr<android::apex::ApexSessionManager> session_manager;
    if (also_include_staged_apexes) {
      session_manager = android::apex::ApexSessionManager::Create(
          android::apex::GetSessionsDir());
      android::apex::InitializeSessionManager(session_manager.get());
    }
    return android::apex::OnOtaChrootBootstrap(also_include_staged_apexes);
  }

  if (strcmp("--snapshotde", argv[1]) == 0) {
    SetDefaultTag("apexd-snapshotde");
    // Need to know if checkpointing is enabled so that a prerestore snapshot
    // can be taken if it's not.
    android::base::Result<android::apex::VoldCheckpointInterface>
        vold_service_st = android::apex::VoldCheckpointInterface::Create();
    if (!vold_service_st.ok()) {
      LOG(ERROR) << "Could not retrieve vold service: "
                 << vold_service_st.error();
    } else {
      android::apex::InitializeVold(&*vold_service_st);
    }

    // We are running regular apexd, which starts after /metadata/apex/sessions
    // and /data/apex/sessions have been created by init. It is safe to create
    // ApexSessionManager.
    auto session_manager = android::apex::ApexSessionManager::Create(
        android::apex::GetSessionsDir());
    android::apex::InitializeSessionManager(session_manager.get());

    int result = android::apex::SnapshotOrRestoreDeUserData();

    if (result == 0) {
      // Notify other components (e.g. init) that all APEXs are ready to be used
      // Note that it's important that the binder service is registered at this
      // point, since other system services might depend on it.
      android::apex::OnAllPackagesReady();
    }
    return result;
  }

  if (strcmp("--vm", argv[1]) == 0) {
    SetDefaultTag("apexd-vm");
    return android::apex::OnStartInVmMode();
  }

  LOG(ERROR) << "Unknown subcommand: " << argv[1];
  return 1;
}

void InstallSigtermSignalHandler() {
  struct sigaction action = {};
  action.sa_handler = [](int /*signal*/) {
    // Handle SIGTERM gracefully.
    // By default, when SIGTERM is received a process will exit with non-zero
    // exit code, which will trigger reboot_on_failure handler if one is
    // defined. This doesn't play well with userspace reboot which might
    // terminate apexd with SIGTERM if apexd was running at the moment of
    // userspace reboot, hence this custom handler to exit gracefully.
    _exit(0);
  };
  sigaction(SIGTERM, &action, nullptr);
}

void InstallSelinuxLogging() {
  union selinux_callback cb;
  cb.func_log = selinux_log_callback;
  selinux_set_callback(SELINUX_CB_LOG, cb);
}

}  // namespace

int main(int argc, char** argv) {
  android::base::InitLogging(argv, &android::base::KernelLogger);
  // TODO(b/158468454): add a -v flag or an external setting to change severity.
  android::base::SetMinimumLogSeverity(android::base::INFO);

  // Two flags are used here:
  // CLI flag `--enable-brand-new-apex`: used to control the feature usage in
  // individual targets
  // AConfig flag `enable_brand_new_apex`: used to advance
  // the feature to different release stages, and applies to all targets
  if (flags::enable_brand_new_apex()) {
    if (argv[1] != nullptr && strcmp("--enable-brand-new-apex", argv[1]) == 0) {
      android::apex::ApexFileRepository::EnableBrandNewApex();
      argc--;
      argv++;
    }
  }

  const bool has_subcommand = argv[1] != nullptr;
  LOG(INFO) << "Started. subcommand = "
            << (has_subcommand ? argv[1] : "(null)");

  // set umask to 022 so that files/dirs created are accessible to other
  // processes e.g.) /apex/apex-info-list.xml is supposed to be read by other
  // processes
  umask(022);

  // In some scenarios apexd needs to adjust the selinux label of the files.
  // Install the selinux logging callback so that we can catch potential errors.
  InstallSelinuxLogging();

  InstallSigtermSignalHandler();

  android::apex::SetConfig(android::apex::kDefaultConfig);

  android::apex::ApexdLifecycle& lifecycle =
      android::apex::ApexdLifecycle::GetInstance();
  bool booting = lifecycle.IsBooting();

  if (has_subcommand) {
    return HandleSubcommand(argc, argv);
  }

  // We are running regular apexd, which starts after /metadata/apex/sessions
  // and /data/apex/sessions have been created by init. It is safe to create
  // ApexSessionManager.
  auto session_manager = android::apex::ApexSessionManager::Create(
      android::apex::GetSessionsDir());
  android::apex::InitializeSessionManager(session_manager.get());

  android::base::Result<android::apex::VoldCheckpointInterface>
      vold_service_st = android::apex::VoldCheckpointInterface::Create();
  android::apex::VoldCheckpointInterface* vold_service = nullptr;
  if (!vold_service_st.ok()) {
    LOG(ERROR) << "Could not retrieve vold service: "
               << vold_service_st.error();
  } else {
    vold_service = &*vold_service_st;
  }
  android::apex::Initialize(vold_service);
  android::apex::InitMetrics(std::make_unique<android::apex::StatsLog>());

  if (booting) {
    auto res = session_manager->MigrateFromOldSessionsDir(
        android::apex::kOldApexSessionsDir);
    if (!res.ok()) {
      LOG(ERROR) << "Failed to migrate sessions to /metadata partition : "
                 << res.error();
    }
    android::apex::OnStart();
  } else {
    // TODO(b/172911822): Trying to use data apex related ApexFileRepository
    //  apis without initializing it should throw error. Also, unit tests should
    //  not pass without initialization.
    // TODO(b/172911822): Consolidate this with Initialize() when
    //  ApexFileRepository can act as cache and re-scanning is not expensive
    android::apex::InitializeDataApex();
  }
  // start apexservice before ApexdLifecycle::WaitForBootStatus which waits for
  // IApexService::markBootComplete().
  android::apex::binder::CreateAndRegisterService();
  android::apex::binder::StartThreadPool();

  if (booting) {
    // Notify other components (e.g. init) that all APEXs are correctly mounted
    // and activated (but are not yet ready to be used). Configuration based on
    // activated APEXs may be performed at this point, but use of APEXs
    // themselves should wait for the ready status instead, which is set when
    // the "--snapshotde" subcommand is received and snapshot/restore is
    // complete.
    android::apex::OnAllPackagesActivated(/*is_bootstrap=*/false);
    lifecycle.WaitForBootStatus(session_manager->HasActiveSession());
    // Run cleanup routine on boot complete.
    // This should run before AllowServiceShutdown() to prevent
    // service_manager killing apexd in the middle of the cleanup.
    android::apex::BootCompletedCleanup();
  }

  android::apex::binder::AllowServiceShutdown();

  android::apex::binder::JoinThreadPool();
  return 1;
}
