// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Description: Linux specific functionality. Other Linux-derivatives layer on
// top of this translation unit.

#include "base/threading/platform_thread.h"

#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include <atomic>
#include <cstdint>
#include <optional>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/process/internal_linux.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_id_name_manager.h"
#include "base/threading/thread_type_delegate.h"
#include "build/build_config.h"

namespace base {

namespace {

ThreadTypeDelegate* g_thread_type_delegate = nullptr;

const FilePath::CharType kCgroupDirectory[] =
    FILE_PATH_LITERAL("/sys/fs/cgroup");

FilePath ThreadTypeToCgroupDirectory(const FilePath& cgroup_filepath,
                                     ThreadType thread_type) {
  switch (thread_type) {
    case ThreadType::kBackground:
    case ThreadType::kUtility:
      return cgroup_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
    case ThreadType::kDefault:
      return cgroup_filepath;
    case ThreadType::kDisplayCritical:
    case ThreadType::kInteractive:
    case ThreadType::kRealtimeAudio:
      return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
  }
  NOTREACHED();
}

void SetThreadCgroup(PlatformThreadId thread_id,
                     const FilePath& cgroup_directory) {
  FilePath tasks_filepath = cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
  std::string tid = NumberToString(thread_id.raw());
  if (!WriteFile(tasks_filepath, as_byte_span(tid))) {
    DVLOG(1) << "Failed to add " << tid << " to " << tasks_filepath.value();
  }
}

void SetThreadCgroupForThreadType(PlatformThreadId thread_id,
                                  const FilePath& cgroup_filepath,
                                  ThreadType thread_type) {
  // Append "chrome" suffix.
  FilePath cgroup_directory = ThreadTypeToCgroupDirectory(
      cgroup_filepath.Append(FILE_PATH_LITERAL("chrome")), thread_type);

  // Silently ignore request if cgroup directory doesn't exist.
  if (!DirectoryExists(cgroup_directory)) {
    return;
  }

  SetThreadCgroup(thread_id, cgroup_directory);
}

}  // namespace

namespace internal {

const ThreadTypeToNiceValuePairForTest kThreadTypeToNiceValueMapForTest[7] = {
    {ThreadType::kRealtimeAudio, -10}, {ThreadType::kDisplayCritical, -8},
    {ThreadType::kDefault, 0},         {ThreadType::kUtility, 2},
    {ThreadType::kBackground, 10},
};

bool CanSetThreadTypeToRealtimeAudio() {
  // Check if root
  if (geteuid() == 0) {
    return true;
  }

  // A non-zero soft-limit on RLIMIT_RTPRIO is required to be allowed to invoke
  // pthread_setschedparam in SetCurrentThreadTypeForPlatform().
  struct rlimit rlim;
  return getrlimit(RLIMIT_RTPRIO, &rlim) != 0 && rlim.rlim_cur != 0;
}

void SetCurrentThreadTypeImpl(ThreadType thread_type,
                              MessagePumpType pump_type_hint) {
  const PlatformThreadId thread_id = PlatformThread::CurrentId();

  if (g_thread_type_delegate &&
      g_thread_type_delegate->HandleThreadTypeChange(thread_id, thread_type)) {
    return;
  }

  internal::SetThreadType(getpid(), thread_id, thread_type, IsViaIPC(false));
}

std::optional<ThreadType> GetCurrentEffectiveThreadTypeForPlatformForTest() {
  int maybe_sched_rr = 0;
  struct sched_param maybe_realtime_prio = {0};
  if (pthread_getschedparam(pthread_self(), &maybe_sched_rr,
                            &maybe_realtime_prio) == 0 &&
      maybe_sched_rr == SCHED_RR &&
      maybe_realtime_prio.sched_priority ==
          PlatformThreadLinux::kRealTimeAudioPrio.sched_priority) {
    return std::make_optional(ThreadType::kRealtimeAudio);
  }
  return std::nullopt;
}

PlatformPriorityOverride SetThreadTypeOverride(
    PlatformThreadHandle thread_handle,
    ThreadType thread_type) {
  return false;
}

void RemoveThreadTypeOverrideImpl(
    const PlatformPriorityOverride& priority_override_handle,
    ThreadType thread_type) {}

}  // namespace internal

// Determine if thread_id is a background thread by looking up whether
// it is in the urgent or non-urgent cpuset.
bool PlatformThreadLinux::IsThreadBackgroundedForTest(
    PlatformThreadId thread_id) {
  FilePath cgroup_filepath(kCgroupDirectory);

  FilePath urgent_cgroup_directory =
      cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset"))
          .Append(FILE_PATH_LITERAL("chrome"))
          .Append(FILE_PATH_LITERAL("urgent"));
  FilePath non_urgent_cgroup_directory =
      cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset"))
          .Append(FILE_PATH_LITERAL("chrome"))
          .Append(FILE_PATH_LITERAL("non-urgent"));

  // Silently ignore request if cgroup directory doesn't exist.
  if (!DirectoryExists(urgent_cgroup_directory) ||
      !DirectoryExists(non_urgent_cgroup_directory)) {
    return false;
  }

  FilePath urgent_tasks_filepath =
      urgent_cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
  FilePath non_urgent_tasks_filepath =
      non_urgent_cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));

  std::string tid = NumberToString(thread_id.raw());
  // Check if thread_id is in the urgent cpuset
  std::string urgent_tasks;
  if (!ReadFileToString(urgent_tasks_filepath, &urgent_tasks)) {
    return false;
  }
  if (urgent_tasks.find(tid) != std::string::npos) {
    return false;
  }

  // Check if thread_id is in the non-urgent cpuset
  std::string non_urgent_tasks;
  if (!ReadFileToString(non_urgent_tasks_filepath, &non_urgent_tasks)) {
    return false;
  }
  if (non_urgent_tasks.find(tid) != std::string::npos) {
    return true;
  }

  return false;
}

void PlatformThreadBase::SetName(const std::string& name) {
  SetNameCommon(name);

  // On linux we can get the thread names to show up in the debugger by setting
  // the process name for the LWP.  We don't want to do this for the main
  // thread because that would rename the process, causing tools like killall
  // to stop working.
  if (PlatformThread::CurrentId().raw() == getpid()) {
    return;
  }

  // http://0pointer.de/blog/projects/name-your-threads.html
  // Set the name for the LWP (which gets truncated to 15 characters).
  // Note that glibc also has a 'pthread_setname_np' api, but it may not be
  // available everywhere and it's only benefit over using prctl directly is
  // that it can set the name of threads other than the current thread.
  int err = prctl(PR_SET_NAME, name.c_str());
  // We expect EPERM failures in sandboxed processes, just ignore those.
  if (err < 0 && errno != EPERM) {
    DPLOG(ERROR) << "prctl(PR_SET_NAME)";
  }
}

// static
void PlatformThreadLinux::SetThreadTypeDelegate(ThreadTypeDelegate* delegate) {
  // A component cannot override a delegate set by another component, thus
  // disallow setting a delegate when one already exists.
  DCHECK(!g_thread_type_delegate || !delegate);

  g_thread_type_delegate = delegate;
}

// static
void PlatformThreadLinux::SetThreadCgroupsForThreadType(
    PlatformThreadId thread_id,
    ThreadType thread_type) {
  FilePath cgroup_filepath(kCgroupDirectory);
  SetThreadCgroupForThreadType(
      thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset")),
      thread_type);
  SetThreadCgroupForThreadType(
      thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("schedtune")),
      thread_type);
}

// static
void PlatformThreadLinux::SetThreadType(ProcessId process_id,
                                        PlatformThreadId thread_id,
                                        ThreadType thread_type,
                                        IsViaIPC via_ipc) {
  internal::SetThreadType(process_id, thread_id, thread_type, via_ipc);
}

namespace internal {
void SetThreadTypeLinux(ProcessId process_id,
                        PlatformThreadId thread_id,
                        ThreadType thread_type,
                        IsViaIPC via_ipc) {
  PlatformThreadLinux::SetThreadCgroupsForThreadType(thread_id, thread_type);

  // Some scheduler syscalls require thread ID of 0 for current thread.
  // This prevents us from requiring to translate the NS TID to
  // global TID.
  PlatformThreadId syscall_tid = thread_id;
  if (thread_id == PlatformThreadLinux::CurrentId()) {
    syscall_tid = kInvalidThreadId;
  }

  if (thread_type == ThreadType::kRealtimeAudio) {
    if (sched_setscheduler(syscall_tid.raw(), SCHED_RR,
                           &PlatformThreadLinux::kRealTimeAudioPrio) == 0) {
      return;
    }
    // If failed to set to RT, fallback to setpriority to set nice value.
    DPLOG(ERROR) << "Failed to set realtime priority for thread " << thread_id;
  }

  SetThreadNiceFromType(thread_id, thread_type);
}

int ThreadTypeToNiceValue(const ThreadType thread_type) {
  switch (thread_type) {
#if BUILDFLAG(ARKWEB_DFX_TRACING)
    case ThreadType::kBackground:
      return 0;
    case ThreadType::kUtility:
      return 0;
    case ThreadType::kDefault:
      return -10;
    case ThreadType::kDisplayCritical:
    case ThreadType::kInteractive:
      return -20;
    case ThreadType::kRealtimeAudio:
      return -20;
#else
    case ThreadType::kBackground:
      return 10;
    case ThreadType::kUtility:
      return 2;
    case ThreadType::kDefault:
      return 0;
    case ThreadType::kDisplayCritical:
    case ThreadType::kInteractive:
      return -8;
    case ThreadType::kRealtimeAudio:
      return -10;
#endif
  }
}

}  // namespace internal

}  // namespace base