/*
 * Copyright 2024 Google LLC
 *
 * 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 "persist.h"

#include "kernel/memory_layout.h"
#include "process_management/process_manager.h"
#include "services/normal/persist.h"
#include "services/normal/settings/settings_file.h"
#include "syscall/syscall.h"
#include "syscall/syscall_internal.h"
#include "util/math.h"


_Static_assert(PERSIST_DATA_MAX_LENGTH <= SETTINGS_VAL_MAX_LEN,
               "PERSIST_DATA_MAX_LENGTH is larger than the max length that "
               "settings_file supports.");


static SettingsFile * prv_lock_and_get_store(void) {
  return persist_service_lock_and_get_store(
      &sys_process_manager_get_current_process_md()->uuid);
}

static void prv_unlock(SettingsFile **store) {
  persist_service_unlock_store(*store);
}

#define LOCK_AND_GET_STORE(name) \
  SettingsFile *name __attribute__((cleanup(prv_unlock))) \
      = prv_lock_and_get_store()


DEFINE_SYSCALL(bool, persist_exists, const uint32_t key) {
  LOCK_AND_GET_STORE(store);
  return settings_file_exists(store, &key, sizeof(key));
}

DEFINE_SYSCALL(int, persist_get_size, const uint32_t key) {
  LOCK_AND_GET_STORE(store);
  int result = settings_file_get_len(store, &key, sizeof(key));
  return result ?: E_DOES_NOT_EXIST;
}

DEFINE_SYSCALL(bool, persist_read_bool, const uint32_t key) {
  bool value = false;
  LOCK_AND_GET_STORE(store);
  settings_file_get(store, &key, sizeof(key), &value, sizeof(value));
  return value;
}

DEFINE_SYSCALL(int32_t, persist_read_int, const uint32_t key) {
  int32_t value = 0;
  LOCK_AND_GET_STORE(store);
  settings_file_get(store, &key, sizeof(key), &value, sizeof(value));
  return value;
}

DEFINE_SYSCALL(int, persist_read_data, const uint32_t key,
               void *buffer, const size_t buffer_size) {
  if (PRIVILEGE_WAS_ELEVATED) {
    syscall_assert_userspace_buffer(buffer, buffer_size);
  }

  LOCK_AND_GET_STORE(store);
  const int len = settings_file_get_len(store, &key, sizeof(key));
  if (len == 0) {
    return E_DOES_NOT_EXIST;
  } else if (FAILED(len)) {
    RETURN_STATUS_UP(len);
  }

  const size_t restricted_size = MIN(buffer_size, (size_t)len);
  const status_t read_result = settings_file_get(
      store, &key, sizeof(key), buffer, restricted_size);
  if (FAILED(read_result)) {
    RETURN_STATUS_UP(read_result);
  }
  return restricted_size;
}

// Legacy version to prevent previous app breakage, __deprecated preserves order
int persist_read_data__deprecated(const uint32_t key,
                                  const size_t buffer_size, void *buffer) {
  return persist_read_data(key, buffer, buffer_size);
}

int persist_read_string(const uint32_t key,
                        char *buffer, const size_t buffer_size) {
  const int read_result = persist_read_data(key, buffer, buffer_size);
  if (PASSED(read_result)) {
    buffer[read_result - 1] = '\0';
  }
  return read_result;
}

// Legacy version to prevent previous app breakage, __deprecated preserves order
int persist_read_string__deprecated(const uint32_t key,
                                    const size_t buffer_size, char *buffer) {
  return persist_read_string(key, buffer, buffer_size);
}

DEFINE_SYSCALL(status_t, persist_write_bool, const uint32_t key, const bool value) {
  LOCK_AND_GET_STORE(store);
  status_t result = settings_file_set(store, &key, sizeof(key),
                                      &value, sizeof(value));
  return PASSED(result) ? (status_t)sizeof(value) : result;
}

DEFINE_SYSCALL(status_t, persist_write_int, const uint32_t key, const int32_t value) {
  LOCK_AND_GET_STORE(store);
  status_t result = settings_file_set(store, &key, sizeof(key),
                                      &value, sizeof(value));
  return PASSED(result) ? (status_t)sizeof(value) : result;
}

// FIXME: PBL-23877 Disallow and document persist write data of length 0 edge case
DEFINE_SYSCALL(int, persist_write_data, const uint32_t key,
                       const void *buffer, const size_t buffer_size) {
  if (PRIVILEGE_WAS_ELEVATED) {
    syscall_assert_userspace_buffer(buffer, buffer_size);
  }
  const size_t restricted_size = MIN(buffer_size, PERSIST_DATA_MAX_LENGTH);
  LOCK_AND_GET_STORE(store);
  int result = settings_file_set(store, &key, sizeof(key),
                                 buffer, restricted_size);
  return PASSED(result) ? (int)restricted_size : result;
}

// Legacy version to prevent previous app breakage, __deprecated preserves order
int persist_write_data__deprecated(const uint32_t key, const size_t buffer_size, const void *buffer) {
  return persist_write_data(key, buffer, buffer_size);
}

DEFINE_SYSCALL(int, persist_write_string, const uint32_t key, const char *cstring) {
  if (PRIVILEGE_WAS_ELEVATED) {
    if (!memory_layout_is_cstring_in_region(
          memory_layout_get_app_region(), cstring, -1)) {
      syscall_failed();
    }
  }
  const size_t cstring_length = strlen(cstring) + 1;
  return persist_write_data(key, cstring, cstring_length);
}

DEFINE_SYSCALL(status_t, persist_delete, const uint32_t key) {
  LOCK_AND_GET_STORE(store);
  status_t result;
  if (settings_file_exists(store, &key, sizeof(key))) {
    result = settings_file_delete(store, &key, sizeof(key));
    if (PASSED(result)) {
      result = S_TRUE;
    }
  } else {
    result = E_DOES_NOT_EXIST;
  }
  return result;
}
