/**
 * @file
 *
 * @ingroup rtems_rfs
 *
 * @brief RTEMS File Systems Open
 * 
 * Open the file system by reading the superblock and then the group data.
 */
/*
 *  COPYRIGHT (c) 2010 Chris Johns <chrisj@rtems.org>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.org/license/LICENSE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include <rtems/rfs/rtems-rfs-data.h>
#include <rtems/rfs/rtems-rfs-file-system.h>
#include <rtems/rfs/rtems-rfs-inode.h>
#include <rtems/rfs/rtems-rfs-trace.h>

uint64_t
rtems_rfs_fs_size (rtems_rfs_file_system* fs)
{
  uint64_t blocks = rtems_rfs_fs_blocks (fs);
  uint64_t block_size = rtems_rfs_fs_block_size (fs);
  return blocks * block_size;
}

uint64_t
rtems_rfs_fs_media_size (rtems_rfs_file_system* fs)
{
  uint64_t media_blocks = (uint64_t) rtems_rfs_fs_media_blocks (fs);
  uint64_t media_block_size = (uint64_t) rtems_rfs_fs_media_block_size (fs);
  return media_blocks * media_block_size;
}

static int
rtems_rfs_fs_read_superblock (rtems_rfs_file_system* fs)
{
  rtems_rfs_buffer_handle handle;
  uint8_t*                sb;
  int                     group;
  int                     rc;

  rc = rtems_rfs_buffer_handle_open (fs, &handle);
  if (rc > 0)
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: handle open failed: %d: %s\n",
              rc, strerror (rc));
    return rc;
  }

  rc = rtems_rfs_buffer_handle_request (fs, &handle, 0, true);
  if (rc > 0)
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: request failed%d: %s\n",
              rc, strerror (rc));
    return rc;
  }

  sb = rtems_rfs_buffer_data (&handle);

#define read_sb(_o) rtems_rfs_read_u32 (sb + (_o))

  if (read_sb (RTEMS_RFS_SB_OFFSET_MAGIC) != RTEMS_RFS_SB_MAGIC)
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: invalid superblock, bad magic\n");
    rtems_rfs_buffer_handle_close (fs, &handle);
    return EIO;
  }

  fs->blocks     = read_sb (RTEMS_RFS_SB_OFFSET_BLOCKS);
  fs->block_size = read_sb (RTEMS_RFS_SB_OFFSET_BLOCK_SIZE);

  if (rtems_rfs_fs_size(fs) > rtems_rfs_fs_media_size (fs))
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: invalid superblock block/size count\n");
    rtems_rfs_buffer_handle_close (fs, &handle);
    return EIO;
  }

  if ((read_sb (RTEMS_RFS_SB_OFFSET_VERSION) & RTEMS_RFS_VERSION_MASK) !=
      (RTEMS_RFS_VERSION * RTEMS_RFS_VERSION_MASK))
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: incompatible version: %08" PRIx32 " (%08" PRIx32 ")\n",
              read_sb (RTEMS_RFS_SB_OFFSET_VERSION), RTEMS_RFS_VERSION_MASK);
    rtems_rfs_buffer_handle_close (fs, &handle);
    return EIO;
  }

  if (read_sb (RTEMS_RFS_SB_OFFSET_INODE_SIZE) != RTEMS_RFS_INODE_SIZE)
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: inode size mismatch: fs:%" PRId32 " target:%" PRId32 "\n",
              read_sb (RTEMS_RFS_SB_OFFSET_VERSION), RTEMS_RFS_VERSION_MASK);
    rtems_rfs_buffer_handle_close (fs, &handle);
    return EIO;
  }

  fs->bad_blocks      = read_sb (RTEMS_RFS_SB_OFFSET_BAD_BLOCKS);
  fs->max_name_length = read_sb (RTEMS_RFS_SB_OFFSET_MAX_NAME_LENGTH);
  fs->group_count     = read_sb (RTEMS_RFS_SB_OFFSET_GROUPS);
  fs->group_blocks    = read_sb (RTEMS_RFS_SB_OFFSET_GROUP_BLOCKS);
  fs->group_inodes    = read_sb (RTEMS_RFS_SB_OFFSET_GROUP_INODES);

  fs->blocks_per_block =
    rtems_rfs_fs_block_size (fs) / sizeof (rtems_rfs_inode_block);

  fs->block_map_singly_blocks =
    fs->blocks_per_block * RTEMS_RFS_INODE_BLOCKS;
  fs->block_map_doubly_blocks =
    fs->blocks_per_block * fs->blocks_per_block * RTEMS_RFS_INODE_BLOCKS;

  fs->inodes = fs->group_count * fs->group_inodes;

  fs->inodes_per_block = fs->block_size / RTEMS_RFS_INODE_SIZE;

  if (fs->group_blocks >
      rtems_rfs_bitmap_numof_bits (rtems_rfs_fs_block_size (fs)))
  {
    rtems_rfs_buffer_handle_close (fs, &handle);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: groups blocks larger than block bits\n");
    return EIO;
  }

  rtems_rfs_buffer_handle_close (fs, &handle);

  /*
   * Change the block size to the value in the superblock.
   */
  rc = rtems_rfs_buffer_setblksize (fs, rtems_rfs_fs_block_size (fs));
  if (rc > 0)
  {
    rtems_rfs_buffer_handle_close (fs, &handle);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: invalid superblock block size%d: %s\n",
              rc, strerror (rc));
    return rc;
  }

  fs->groups = calloc (fs->group_count, sizeof (rtems_rfs_group));

  if (!fs->groups)
  {
    rtems_rfs_buffer_handle_close (fs, &handle);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: read-superblock: no memory for group table\n");
    return ENOMEM;
  }

  /*
   * Perform each phase of group initialisation at the same time. This way we
   * know how far the initialisation has gone if an error occurs and we need to
   * close everything.
   */
  for (group = 0; group < fs->group_count; group++)
  {
    rc = rtems_rfs_group_open (fs,
                               rtems_rfs_fs_block (fs, group, 0),
                               fs->group_blocks,
                               fs->group_inodes,
                               &fs->groups[group]);
    if (rc > 0)
    {
      int g;
      for (g = 0; g < group; g++)
        rtems_rfs_group_close (fs, &fs->groups[g]);
      rtems_rfs_buffer_handle_close (fs, &handle);
      if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
        printf ("rtems-rfs: read-superblock: no memory for group table%d: %s\n",
                rc, strerror (rc));
      return rc;
    }
  }

  return 0;
}

int
rtems_rfs_fs_open (const char*             name,
                   void*                   user,
                   uint32_t                flags,
                   uint32_t                max_held_buffers,
                   rtems_rfs_file_system** fs)
{
#if UNUSED
  rtems_rfs_group*       group;
  size_t                 group_base;
#endif
  rtems_rfs_inode_handle inode;
  uint16_t               mode;
  int                    rc;

  if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
    printf ("rtems-rfs: open: %s\n", name);

  *fs = malloc (sizeof (rtems_rfs_file_system));
  if (!*fs)
  {
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: open: no memory for file system data\n");
    errno = ENOMEM;
    return -1;
  }

  memset (*fs, 0, sizeof (rtems_rfs_file_system));

  (*fs)->user = user;
  rtems_chain_initialize_empty (&(*fs)->buffers);
  rtems_chain_initialize_empty (&(*fs)->release);
  rtems_chain_initialize_empty (&(*fs)->release_modified);
  rtems_chain_initialize_empty (&(*fs)->file_shares);

  (*fs)->max_held_buffers = max_held_buffers;
  (*fs)->buffers_count = 0;
  (*fs)->release_count = 0;
  (*fs)->release_modified_count = 0;
  (*fs)->flags = flags;

#if UNUSED
  group = &(*fs)->groups[0];
  group_base = 0;
#endif

  /*
   * Open the buffer interface.
   */
  rc = rtems_rfs_buffer_open (name, *fs);
  if (rc > 0)
  {
    free (*fs);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: open: buffer open failed: %d: %s\n",
              rc, strerror (rc));
    errno = rc;
    return -1;
  }

  rc = rtems_rfs_fs_read_superblock (*fs);
  if (rc > 0)
  {
    rtems_rfs_buffer_close (*fs);
    free (*fs);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: open: reading superblock: %d: %s\n",
              rc, strerror (rc));
    errno = rc;
    return -1;
  }

  rc = rtems_rfs_inode_open (*fs, RTEMS_RFS_ROOT_INO, &inode, true);
  if (rc > 0)
  {
    rtems_rfs_buffer_close (*fs);
    free (*fs);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: open: reading root inode: %d: %s\n",
              rc, strerror (rc));
    errno = rc;
    return -1;
  }

  if (((*fs)->flags & RTEMS_RFS_FS_FORCE_OPEN) == 0)
  {
    mode = rtems_rfs_inode_get_mode (&inode);

    if ((mode == 0xffff) || !RTEMS_RFS_S_ISDIR (mode))
    {
      rtems_rfs_inode_close (*fs, &inode);
      rtems_rfs_buffer_close (*fs);
      free (*fs);
      if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
        printf ("rtems-rfs: open: invalid root inode mode\n");
      errno = EIO;
      return -1;
    }
  }

  rc = rtems_rfs_inode_close (*fs, &inode);
  if (rc > 0)
  {
    rtems_rfs_buffer_close (*fs);
    free (*fs);
    if (rtems_rfs_trace (RTEMS_RFS_TRACE_OPEN))
      printf ("rtems-rfs: open: closing root inode: %d: %s\n", rc, strerror (rc));
    errno = rc;
    return -1;
  }

  return 0;
}

int
rtems_rfs_fs_close (rtems_rfs_file_system* fs)
{
  int group;

  if (rtems_rfs_trace (RTEMS_RFS_TRACE_CLOSE))
    printf ("rtems-rfs: close\n");

  for (group = 0; group < fs->group_count; group++)
    rtems_rfs_group_close (fs, &fs->groups[group]);

  rtems_rfs_buffer_close (fs);

  free (fs);
  return 0;
}
