/*
 * 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 "server_configurable_flags/disaster_recovery.h"
#include "server_configurable_flags/get_flags.h"
#include "server_configurable_flags/get_cflags.h"

#if defined(__BIONIC__)
#include <cutils/properties.h>
#endif  // __BIONIC__
#include <cctype>
#include <cstring>
#include <string>

#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/properties.h"
#include "android-base/strings.h"
#include "android-base/unique_fd.h"

#define SYSTEM_PROPERTY_PREFIX "persist.device_config."

#define ATTEMPTED_BOOT_COUNT_PROPERTY "persist.device_config.attempted_boot_count"

#define RESET_PERFORMED_PROPERTY "device_config.reset_performed"

#define RESET_FLAGS_FILE_PATH "/data/server_configurable_flags/reset_flags"

#define ATTEMPTED_BOOT_COUNT_THRESHOLD 4

namespace server_configurable_flags {

static std::string MakeSystemPropertyName(const std::string& experiment_category_name,
                                          const std::string& experiment_flag_name) {
  return SYSTEM_PROPERTY_PREFIX + experiment_category_name + "." + experiment_flag_name;
}

static bool ValidateCharacters(const std::string& segment) {
  for (char c : segment) {
    if (!isalnum(c) && !strchr(":@_.-", c)) {
      return false;
    }
  }
  return true;
}

static bool ValidateExperimentSegment(const std::string& segment) {
  return ValidateCharacters(segment) && !segment.empty() && segment[0] != '.' &&
         *segment.rbegin() != '.';
}

#if defined(__BIONIC__)
static void ResetFlag(const char* key, const char* value, void* cookie) {
  if (strcmp(ATTEMPTED_BOOT_COUNT_PROPERTY, key) &&
      android::base::StartsWith(key, SYSTEM_PROPERTY_PREFIX) && strlen(value) > 0 &&
      android::base::SetProperty(key, "")) {
    std::string* reset_flags = static_cast<std::string*>(cookie);
    if (reset_flags->length() > 0) {
      reset_flags->append(";");
    }
    reset_flags->append(key);
    android::base::SetProperty(RESET_PERFORMED_PROPERTY, "true");
  }
}

// Reset all system properties used as flags into empty value,
// and record reset flags' names in /data/server_configurable_flags/reset_flags
static void ResetAllFlags() {
  std::string reset_flags;
  property_list(ResetFlag, &reset_flags);

  if (reset_flags.length() > 0) {
    android::base::unique_fd fd(
        TEMP_FAILURE_RETRY(open(RESET_FLAGS_FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0666)));
    if (fd == -1) {
      LOG(INFO) << __FUNCTION__ << " failed to open file " << RESET_FLAGS_FILE_PATH;
    } else if (!WriteStringToFd(reset_flags, fd)) {
      LOG(INFO) << __FUNCTION__ << " failed to write file " << RESET_FLAGS_FILE_PATH;
    } else {
      LOG(INFO) << __FUNCTION__ << " successfully write to file " << RESET_FLAGS_FILE_PATH;
    }
  }
}
#endif  // __BIONIC__

void ServerConfigurableFlagsReset(ResetMode reset_mode) {
  LOG(INFO) << __FUNCTION__ << " reset_mode value: " << reset_mode;
#if defined(__BIONIC__)
  if (reset_mode == BOOT_FAILURE) {
    int fail_count = android::base::GetIntProperty(ATTEMPTED_BOOT_COUNT_PROPERTY, 0);
    if (fail_count < ATTEMPTED_BOOT_COUNT_THRESHOLD) {
      LOG(INFO) << __FUNCTION__ << " attempted boot count is under threshold, skipping reset.";

      // ATTEMPTED_BOOT_COUNT_PROPERTY will be reset to 0 when sys.boot_completed is set to 1.
      // The code lives in flags_health_check.rc.
      android::base::SetProperty(ATTEMPTED_BOOT_COUNT_PROPERTY, std::to_string(fail_count + 1));
    } else {
      LOG(INFO) << __FUNCTION__ << " attempted boot count reaches threshold, resetting flags.";
      ResetAllFlags();
    }
  } else if (reset_mode == UPDATABLE_CRASHING) {
    LOG(INFO) << __FUNCTION__ << " updatable crashing detected, resetting flags.";
    ResetAllFlags();
  } else {
    LOG(ERROR) << __FUNCTION__ << " invalid reset_mode, skipping reset.";
  }
#else
  LOG(ERROR) << __FUNCTION__ << " ServerConfigurableFlagsReset is not available for this build.";
#endif  // __BIONIC__
}

std::string GetServerConfigurableFlag(const std::string& experiment_category_name,
                                      const std::string& experiment_flag_name,
                                      const std::string& default_value) {
  if (!ValidateExperimentSegment(experiment_category_name)) {
    LOG(ERROR) << __FUNCTION__ << " invalid category name " << experiment_category_name;
    return default_value;
  }
  if (!ValidateExperimentSegment(experiment_flag_name)) {
    LOG(ERROR) << __FUNCTION__ << " invalid flag name " << experiment_flag_name;
    return default_value;
  }
  return android::base::GetProperty(
      MakeSystemPropertyName(experiment_category_name, experiment_flag_name), default_value);
}
}  // namespace server_configurable_flags

const char* server_configurable_flags_GetServerConfigurableFlag(
    const char* experiment_category_name,
    const char* experiment_flag_name,
    const char* default_value) {
  auto val = server_configurable_flags::GetServerConfigurableFlag(
      std::string(experiment_category_name),
      std::string(experiment_flag_name),
      std::string(default_value));
  char* ret = (char*)malloc(sizeof(char) * (val.size()+1));
  memcpy(ret, val.c_str(), val.size()+1);
  return ret;
}
