/*
 * 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.
 */

#include "src/profiling/memory/system_property.h"

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/utils.h"

#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
#include <sys/system_properties.h>
#endif

namespace perfetto {
namespace profiling {

SystemProperties::Handle::Handle(Handle&& other) noexcept {
  system_properties_ = other.system_properties_;
  property_ = std::move(other.property_);
  all_ = other.all_;
  other.system_properties_ = nullptr;
}

SystemProperties::Handle& SystemProperties::Handle::operator=(
    Handle&& other) noexcept {
  // Construct this temporary because the RHS could be an lvalue cast to an
  // rvalue reference whose lifetime we do not know.
  Handle tmp(std::move(other));
  using std::swap;
  swap(*this, tmp);
  return *this;
}

SystemProperties::Handle::Handle(SystemProperties* system_properties)
    : system_properties_(system_properties), all_(true) {}

SystemProperties::Handle::Handle(SystemProperties* system_properties,
                                 std::string property)
    : system_properties_(system_properties), property_(std::move(property)) {}

SystemProperties::Handle::~Handle() {
  if (system_properties_) {
    if (all_)
      system_properties_->UnsetAll();
    else
      system_properties_->UnsetProperty(property_);
  }
}

SystemProperties::Handle::operator bool() {
  return system_properties_ != nullptr;
}

SystemProperties::Handle SystemProperties::SetProperty(std::string name) {
  auto it = properties_.find(name);
  if (it == properties_.end()) {
    if (!SetAndroidProperty("heapprofd.enable." + name, "1"))
      return Handle(nullptr);
    if (properties_.size() == 1 || alls_ == 0) {
      if (!SetAndroidProperty("heapprofd.enable", "1"))
        return Handle(nullptr);
    }
    properties_.emplace(name, 1);
  } else {
    it->second++;
  }
  return Handle(this, std::move(name));
}

SystemProperties::Handle SystemProperties::SetAll() {
  if (alls_ == 0) {
    if (!SetAndroidProperty("heapprofd.enable", "all"))
      return Handle(nullptr);
  }
  alls_++;
  return Handle(this);
}

// This is conditionally noreturn, so disable the warning.
#pragma GCC diagnostic push
#if PERFETTO_DCHECK_IS_ON()
#pragma GCC diagnostic ignored "-Wmissing-noreturn"
#endif

// static
void SystemProperties::ResetHeapprofdProperties() {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
  int r = __system_property_foreach(
      [](const prop_info* pi, void*) {
        __system_property_read_callback(
            pi,
            [](void*, const char* name, const char*, uint32_t) {
              constexpr char kDebugModePropName[] = "heapprofd.userdebug.mode";

              // Unset everything starting with "heapprofd.", except for the
              // property stating which mode to use on debug builds.
              const char* found = strstr(name, "heapprofd.");
              if (found == name && strncmp(name, kDebugModePropName,
                                           strlen(kDebugModePropName))) {
                int ret = __system_property_set(name, "");
                PERFETTO_DCHECK(ret == 0);
              }
            },
            nullptr);
      },
      nullptr);
  PERFETTO_DCHECK(r == 0);
#else
  PERFETTO_DFATAL_OR_ELOG(
      "Cannot ResetHeapprofdProperties on out-of-tree builds.");
#endif
}

#pragma GCC diagnostic pop

SystemProperties::~SystemProperties() {
  PERFETTO_DCHECK(alls_ == 0 && properties_.empty());
}

bool SystemProperties::SetAndroidProperty(const std::string& name,
                                          const std::string& value) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
  return __system_property_set(name.c_str(), value.c_str()) == 0;
#else
  // Allow this to be mocked out for tests on other platforms.
  base::ignore_result(name);
  base::ignore_result(value);
  PERFETTO_FATAL("Properties can only be set on Android.");
#endif
}

void SystemProperties::UnsetProperty(const std::string& name) {
  auto it = properties_.find(name);
  if (it == properties_.end()) {
    PERFETTO_DFATAL_OR_ELOG("Unsetting unknown property.");
    return;
  }
  if (--(it->second) == 0) {
    properties_.erase(it);
    SetAndroidProperty("heapprofd.enable." + name, "");
    if (properties_.empty() && alls_ == 0)
      SetAndroidProperty("heapprofd.enable", "");
  }
}

void SystemProperties::UnsetAll() {
  if (--alls_ == 0) {
    if (properties_.empty())
      SetAndroidProperty("heapprofd.enable", "");
    else
      SetAndroidProperty("heapprofd.enable", "1");
  }
}

void swap(SystemProperties::Handle& a, SystemProperties::Handle& b) {
  using std::swap;
  swap(a.system_properties_, b.system_properties_);
  swap(a.property_, b.property_);
  swap(a.all_, b.all_);
}

}  // namespace profiling
}  // namespace perfetto
