/**
 * @file fs_po.c
 * @brief Persistent object filesystem functionality implementation
 * @author Iaroslav Makarchuk (i.makarchuk@samsung.com)
 * @date Created Oct 3, 2016
 * @par In Samsung Ukraine R&D Center (SURC) under a contract between
 * @par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * @par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * @par Copyright: (c) Samsung Electronics Co, Ltd 2015. All rights reserved.
 *
 * This software is proprietary of Samsung Electronics.
 * No part of this software, either material or conceptual may be copied
 * or distributed, transmitted, transcribed, stored in a retrieval system
 * or translated into any human or computer language in any form by any means,
 * electronic, mechanical, manual or otherwise, or disclosed to third parties
 * without the express written permission of Samsung Electronics.
 *
 * The file was taken from tzsl repo and modified for Multibuild project's
 * needs.
 */

#include <dirent.h>
#include <errno.h>
#include <fs_utils.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <tees_log.h>
#include <unistd.h>
#include <time.h>

#define __USE_XOPEN_EXTENDED
#define _XOPEN_SOURCE  500
#include <ftw.h>

#ifndef MB_PO_ROOT_DIR
  #define MB_PO_ROOT_DIR  "/efs/mb_po"
#endif

#ifndef MB_PO_DEFAULT_UID
  #define MB_PO_DEFAULT_UID  1000
#endif

#ifndef MB_PO_DEFAULT_GID
  #define MB_PO_DEFAULT_GID  1000
#endif

#define FTW_FD_LIMIT         64
#define MAX_ID_LEN           64

static const char kHexChars[] = "0123456789abcdef0123456789ABCDEF";

static const char *kPOStoreNames[] = {
  "data",
  "metadata",
  "attrib"
};

/**
 * HexToAscii(in, out, len):
 * Convert ${len} bytes from ${in} into hexadecimal, writing the resulting
 * 2 * ${len} bytes to ${out}; and append a NULL byte.
 * In this implementation we limit the maximum length to 256 bytes, in fact
 * we don't need more than that.
 */

#define MAX_HEXBUF_LEN 256

static int HexToAscii(char *out, uint32_t out_len,
                      const unsigned char *in, size_t len) {
  char *p = out;
  size_t i;
  int result = -1;

  if ((len > MAX_HEXBUF_LEN) || (out_len < len * 2)) {
    goto exit;
  }

  for (i = 0; i < len; i++) {
    *p++ = kHexChars[in[i] >> 4];
    *p++ = kHexChars[in[i] & 0x0f];
  }

  *p = '\0';
  result = 0;

exit:
  return result;
}

static int Hex2Bin(const char *hex_str, uint32_t len,
                   uint8_t *buffer, uint32_t *buffer_len) {
  uint32_t i = 0;
  int result = -1;

  if (!hex_str || !buffer || !buffer_len || *buffer_len < len / 2) {
    goto exit;
  }

  for (i = 0; i < len; i += 2) {
    if (sscanf(hex_str + i, "%02hhx", &(buffer[i >> 1])) < 1) {
      goto exit;
    }
  }

  *buffer_len = i / 2;
  result = 0;

exit:
  return result;
}

static int Mkdir(const char *path) {
  if (mkdir(path, 0700) && (errno != EEXIST)) {
    return -1;
  }

  MultiBuildChown(path, strlen(path), MB_PO_DEFAULT_UID, MB_PO_DEFAULT_GID);

  return 0;
}

static int CreateDirs(const uint8_t *owner_uuid, uint32_t owner_uuid_len) {
  int retval = -1;
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };

  if (!owner_uuid || !owner_uuid_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  snprintf(path_str, MB_FS_MAX_PATH_LEN - 1, "%s/%s",
           MB_PO_ROOT_DIR, owner_uuid_str);

  retval = Mkdir(MB_PO_ROOT_DIR);
  retval |= Mkdir(path_str);

exit:
  return retval;
}

MultibuildFsResult FsPoStore(const uint8_t *owner_uuid,
                             uint32_t owner_uuid_len,
                             const uint8_t *po_id, uint32_t po_id_len,
                             const void *data, uint32_t data_len,
                             MultibuildFsStoreDataType data_type) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char po_id_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !po_id || !po_id_len || !data || !data_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  if (HexToAscii(po_id_str, sizeof(po_id_str), po_id, po_id_len)) {
    goto exit;
  }

  /* Object files path */
  snprintf(path_str, MB_FS_MAX_PATH_LEN - 1, "%s/%s/.%s/%s", MB_PO_ROOT_DIR,
           owner_uuid_str, po_id_str, kPOStoreNames[data_type]);

  result = MultiBuildWriteFileAtomic(path_str,
                                     strlen(path_str), data, data_len);

  MultiBuildChown(path_str, strlen(path_str),
                  MB_PO_DEFAULT_UID, MB_PO_DEFAULT_GID);
exit:
  return result;
}

MultibuildFsResult ReeTimeGet(MB_Time *time) {
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;
  struct timespec ts = { 0, 0 };

  if (!time) {
    goto exit;
  }
  clock_gettime(CLOCK_REALTIME, &ts);
  time->seconds = ts.tv_sec;
  time->millis = ts.tv_nsec / 1000000;
  result = MB_FS_OK;
exit:
  return result;
}

MultibuildFsResult FsPoRead(const uint8_t *owner_uuid,
                            uint32_t owner_uuid_len,
                            const uint8_t *po_id, uint32_t po_id_len,
                            void *data, uint32_t *data_len,
                            MultibuildFsStoreDataType data_type) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char po_id_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !po_id || !po_id_len || !data || !data_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  if (HexToAscii(po_id_str, sizeof(po_id_str), po_id, po_id_len)) {
    goto exit;
  }

  snprintf(path_str, sizeof(path_str) - 1, "%s/%s/%s/%s", MB_PO_ROOT_DIR,
           owner_uuid_str, po_id_str, kPOStoreNames[data_type]);
  result = MultiBuildReadFile(path_str,
                              strlen(path_str), data, data_len);

exit:
  return result;
}

static int ftw_callback(const char *file_path,
                        const struct stat *file_stat,
                        int type_flag, struct FTW *ftw_buffer) {
  (void)file_stat;
  (void)type_flag;
  (void)ftw_buffer;

  return remove(file_path);
}

static MultibuildFsResult RemoveDirectory(const char *path) {
  MultibuildFsResult result = MB_FS_OK;
  int ftw_res = nftw(path, ftw_callback, FTW_FD_LIMIT, FTW_DEPTH | FTW_PHYS);

  if (ftw_res) {
    switch (errno) {
      case ENOENT: {
        result = MB_FS_ERROR_NOT_FOUND;
        break;
      }
      default: {
        result = MB_FS_ERROR_GENERIC;
        break;
      }
    }
  }

  return result;
}

MultibuildFsResult FsPoRemove(const uint8_t *owner_uuid,
                              uint32_t owner_uuid_len,
                              const uint8_t *po_id, uint32_t po_id_len) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char po_id_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !po_id || !po_id_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  if (HexToAscii(po_id_str, sizeof(po_id_str), po_id, po_id_len)) {
    goto exit;
  }

  snprintf(path_str, sizeof(path_str) - 1, "%s/%s/%s",
           MB_PO_ROOT_DIR, owner_uuid_str, po_id_str);

  result = RemoveDirectory(path_str);

exit:
  return result;
}

MultibuildFsResult FsPoInitUpdate(const uint8_t *owner_uuid,
                                  uint32_t owner_uuid_len,
                                  const uint8_t *po_id, uint32_t po_id_len) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char po_id_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !po_id || !po_id_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  if (HexToAscii(po_id_str, sizeof(po_id_str), po_id, po_id_len)) {
    goto exit;
  }

  if (CreateDirs(owner_uuid, owner_uuid_len)) {
    result = MB_FS_ERROR_GENERIC;
    goto exit;
  }

  /* Object directory path */
  snprintf(path_str, MB_FS_MAX_PATH_LEN - 1, "%s/%s/.%s",
           MB_PO_ROOT_DIR, owner_uuid_str, po_id_str);

  if (Mkdir(path_str)) {
    result = MB_FS_ERROR_GENERIC;
    goto exit;
  }

  result = MB_FS_OK;

exit:
  return result;
}

MultibuildFsResult FsPoAbortUpdate(const uint8_t *owner_uuid,
                                   uint32_t owner_uuid_len,
                                   const uint8_t *po_id, uint32_t po_id_len) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char po_id_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !po_id || !po_id_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  if (HexToAscii(po_id_str, sizeof(po_id_str), po_id, po_id_len)) {
    goto exit;
  }

  snprintf(path_str, sizeof(path_str) - 1, "%s/%s/.%s",
           MB_PO_ROOT_DIR, owner_uuid_str, po_id_str);

  result = RemoveDirectory(path_str);

  if (MB_FS_OK != result && MB_FS_ERROR_NOT_FOUND != result) {
    goto exit;
  }

  result = MB_FS_OK;

exit:
  return result;
}

MultibuildFsResult FsPoCommitUpdate(const uint8_t *owner_uuid,
                                    uint32_t owner_uuid_len,
                                    const uint8_t *po_id, uint32_t po_id_len) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char po_id_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char tmp_path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !po_id || !po_id_len) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  if (HexToAscii(po_id_str, sizeof(po_id_str), po_id, po_id_len)) {
    goto exit;
  }

  snprintf(path_str, sizeof(path_str) - 1, "%s/%s/%s",
           MB_PO_ROOT_DIR, owner_uuid_str, po_id_str);

  snprintf(tmp_path_str, sizeof(path_str) - 1, "%s/%s/.%s",
           MB_PO_ROOT_DIR, owner_uuid_str, po_id_str);

  result = RemoveDirectory(path_str);

  if (MB_FS_OK != result && MB_FS_ERROR_NOT_FOUND != result) {
    goto exit;
  }

  if (rename(tmp_path_str, path_str)) {
    result = MB_FS_ERROR_GENERIC;
    goto exit;
  }

  result = MB_FS_OK;

exit:
  return result;
}

MultibuildFsResult FsPoList(const uint8_t *owner_uuid,
                            uint32_t owner_uuid_len,
                            uint8_t *blob, uint32_t *blob_len) {
  char owner_uuid_str[MB_FS_MAX_PATH_LEN] = { 0 };
  char path_str[MB_FS_MAX_PATH_LEN] = { 0 };
  DIR *directory;
  struct dirent *entry;
  uint32_t po_count = 0;
  uint32_t blob_offset = sizeof(uint32_t);
  struct stat path_stat;

  MultibuildFsResult result = MB_FS_ERROR_BAD_PARAMETERS;

  if (!owner_uuid || !owner_uuid_len ||
      !blob || !blob_len || *blob_len < sizeof(uint32_t)) {
    goto exit;
  }

  if (HexToAscii(owner_uuid_str, sizeof(owner_uuid_str),
                 owner_uuid, owner_uuid_len)) {
    goto exit;
  }

  snprintf(path_str, sizeof(path_str) - 1, "%s/%s",
           MB_PO_ROOT_DIR, owner_uuid_str);

  directory = opendir(path_str);
  if (!directory) {
    result = MB_FS_ERROR_NOT_FOUND;
    goto exit;
  }

  while ((entry = readdir(directory)) != NULL) {
    int stat_res = stat(entry->d_name, &path_stat);

    if (!stat_res && S_ISDIR(path_stat.st_mode) && entry->d_name[0] != '.') {
      /* Record: 4 bytes len + id */
      uint32_t record_len = strlen(entry->d_name) + sizeof(uint32_t);
      uint8_t id[MAX_ID_LEN] = { 0 };
      uint32_t id_len = sizeof(id);

      if (blob_offset + record_len > *blob_len) {
        result = MB_FS_ERROR_SHORT_BUFFER;
        closedir(directory);
        goto exit;
      }

      if (Hex2Bin(entry->d_name, strlen(entry->d_name), id, &id_len)) {
        result = MB_FS_ERROR_GENERIC;
        closedir(directory);
        goto exit;
      }

      memcpy(blob + blob_offset, &id_len, sizeof(id_len));
      blob_offset += sizeof(uint32_t);

      memcpy(blob + blob_offset, id, id_len);
      blob_offset += id_len;

      po_count++;
    }
  }
  closedir(directory);

  memcpy(blob, &po_count, sizeof(po_count));
  *blob_len = blob_offset;
  result = MB_FS_OK;
exit:
  return result;
}
