// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/updater/mac/keystone/ksinstall.h"

#import <Foundation/Foundation.h>
#import <getopt.h>
#import <stdio.h>

#include <optional>
#include <string>

#include "base/at_exit.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/message_pump_type.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "chrome/updater/app/app.h"
#include "chrome/updater/get_updater_scope.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/util/mac_util.h"
#include "chrome/updater/util/util.h"

namespace updater {
namespace {

class KSInstallApp : public App {
 public:
  KSInstallApp(int argc, char* argv[]) : argc_(argc), argv_(argv) {}

 private:
  ~KSInstallApp() override = default;
  void FirstTaskRun() override;

  bool ParseCommandLineOptions();
  void Uninstall(base::OnceCallback<void(int)> callback);

  int argc_;
  raw_ptr<char*> argv_;

  bool uninstall_;
};

void KSInstallApp::Uninstall(base::OnceCallback<void(int)> callback) {
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock()}, base::BindOnce([] {
        const std::optional<base::FilePath>& keystone_path =
            GetKeystoneFolderPath((geteuid() == 0) ? UpdaterScope::kSystem
                                                   : UpdaterScope::kUser);
        if (!keystone_path ||
            !base::DeletePathRecursively(
                keystone_path->Append(KEYSTONE_NAME ".bundle"))) {
          PLOG(ERROR) << "Couldn't find/delete Keystone path.";
          return false;
        }
        return true;
      }),
      base::BindOnce(
          [](base::OnceCallback<void(int)> cb, bool result) {
            if (result) {
              std::move(cb).Run(0);
            } else {
              LOG(ERROR) << "Deleting Keystone at path failed";
              std::move(cb).Run(1);
            }
          },
          std::move(callback)));
}

bool KSInstallApp::ParseCommandLineOptions() {
  base::CommandLine::Init(argc_, argv_);
  const base::CommandLine* command_line =
      base::CommandLine::ForCurrentProcess();
  const base::CommandLine::StringVector args = command_line->GetArgs();
  if (command_line->HasSwitch("uninstall")) {
    uninstall_ = true;
    return true;
  }

  return false;
}

void KSInstallApp::FirstTaskRun() {
  if (!ParseCommandLineOptions()) {
    LOG(ERROR) << "Expected --uninstall switch.";
    Shutdown(1);
    return;
  }

  if ((geteuid() == 0) && (getuid() != 0)) {
    if (setuid(0) || setgid(0)) {
      LOG(ERROR) << "Can't setuid()/setgid() appropriately.";
      Shutdown(1);
      return;
    }
  }

  CHECK(uninstall_);
  Uninstall(base::BindOnce(&KSInstallApp::Shutdown, this));
}

scoped_refptr<App> MakeKSInstallApp(int argc, char* argv[]) {
  return base::MakeRefCounted<KSInstallApp>(argc, argv);
}

}  // namespace

int KSInstallMain(int argc, char* argv[]) {
  base::AtExitManager exit_manager;
  base::CommandLine::Init(argc, argv);
  updater::InitLogging(GetUpdaterScope());
  InitializeThreadPool("keystone");
  const base::ScopedClosureRunner shutdown_thread_pool(
      base::BindOnce([] { base::ThreadPoolInstance::Get()->Shutdown(); }));
  base::SingleThreadTaskExecutor main_task_executor(
      base::MessagePumpType::DEFAULT, true);
  return MakeKSInstallApp(argc, argv)->Run();
}

}  // namespace updater
