// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 Eduardo Aguiar
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "filesystem_impl_ext2.h"
#include <mobius/decoder/data_decoder.h>
#include <mobius/string_functions.h>

namespace mobius
{
namespace filesystem
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief check if stream contains an instance of ext2/3/4 filesystem
//! \param reader stream reader
//! \param offset offset from the beginning of the stream
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
filesystem_impl_ext2::is_instance (mobius::io::reader reader, std::uint64_t offset)
{
  constexpr int MAGIC_OFFSET = 0x0438;

  reader.seek (offset + MAGIC_OFFSET);
  auto data = reader.read (2);

  return data == "\x53\xef";
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief constructor
//! \param reader stream reader
//! \param offset offset from the beginning of the stream
//! \see https://github.com/torvalds/linux/blob/master/fs/ext4/ext4.h
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
filesystem_impl_ext2::filesystem_impl_ext2 (mobius::io::reader reader, std::uint64_t offset)
  : filesystem_impl_base (reader, offset),
    tsk_adaptor (reader, offset)
{
  // create decoder
  reader.seek (offset + 1024);
  mobius::decoder::data_decoder decoder (reader);

  // decode superblock
  inodes_count_ = decoder.get_uint32_le ();
  blocks_count_ = decoder.get_uint32_le ();
  root_blocks_count_ = decoder.get_uint32_le ();
  free_blocks_count_ = decoder.get_uint32_le ();
  free_inodes_count_ = decoder.get_uint32_le ();
  first_data_block_ = decoder.get_uint32_le ();
  block_size_ = (1 << (10 + decoder.get_uint32_le ()));
  cluster_size_ = (1 << decoder.get_uint32_le ()) * block_size_;
  blocks_per_group_ = decoder.get_uint32_le ();
  clusters_per_group_ = decoder.get_uint32_le ();
  inodes_per_group_ = decoder.get_uint32_le ();
  last_mount_time_ = decoder.get_unix_datetime ();
  last_write_time_ = decoder.get_unix_datetime ();
  mount_count_ = decoder.get_uint16_le ();
  max_mount_count_ = decoder.get_int16_le ();
  decoder.skip (2);             // s_magic
  state_ = decoder.get_uint16_le ();
  errors_ = decoder.get_uint16_le ();
  minor_revision_level_ = decoder.get_uint16_le ();
  last_check_time_ = decoder.get_unix_datetime ();
  check_interval_ = decoder.get_uint32_le ();
  creator_os_ = decoder.get_uint32_le ();
  revision_level_ = decoder.get_uint32_le ();
  def_resuid_ = decoder.get_uint16_le ();
  def_resgid_ = decoder.get_uint16_le ();
  first_inode_ = decoder.get_uint32_le ();
  inode_size_ = decoder.get_uint16_le ();
  block_group_number_ = decoder.get_uint16_le ();
  feature_compat_ = decoder.get_uint32_le ();
  feature_incompat_ = decoder.get_uint32_le ();
  feature_read_only_ = decoder.get_uint32_le ();
  volume_uuid_ = decoder.get_uuid ();
  volume_name_ = decoder.get_string_by_size (16);
  last_mount_point_ = decoder.get_string_by_size (64);
  decoder.skip (8);             // s_algorithm_usage_bitmap...s_reserved_gdt_blocks
  journal_uuid_ = decoder.get_uuid ();
  journal_inode_ = decoder.get_uint32_le ();
  journal_device_ = decoder.get_uint32_le ();
  decoder.skip (20);            // s_last_orphan...s_hash_seed[4]
  default_hash_version_ = decoder.get_uint8 ();
  decoder.skip (3);             // s_jnl_backup_type, s_desc_size
  default_mount_options_ = decoder.get_uint32_le ();
  decoder.skip (4);             // s_first_meta_bg
  creation_time_ = decoder.get_unix_datetime ();
  decoder.skip (17*4);          // s_jnl_blocks[17]
  blocks_count_ |= std::uint64_t (decoder.get_uint32_le ());
  root_blocks_count_ |= std::uint64_t (decoder.get_uint32_le ());
  free_blocks_count_ |= std::uint64_t (decoder.get_uint32_le ());
  decoder.skip (28);            // s_min_extra_isize...s_reserved_pad
  kib_written_ = decoder.get_uint64_le ();
  decoder.skip (20);            // s_snapshot_inum...s_snapshot_list
  error_count_ = decoder.get_uint32_le ();
  first_error_time_ = decoder.get_unix_datetime ();
  decoder.skip (48);            // s_first_error_ino...s_first_error_line
  last_error_time_ = decoder.get_unix_datetime ();
  decoder.skip (48);            // s_last_error_ino...s_last_error_func[32]
  mount_options_ = decoder.get_string_by_size (64);
  decoder.skip (20);            // s_usr_quota_inum...s_backup_bgs[2]
  encryption_algorithms_ = decoder.get_bytearray_by_size (4);
  decoder.skip (420);           // s_encrypt_pw_salt[16]..s_reserved[98]
  checksum_ = decoder.get_uint32_le ();

  // derived information
  set_size (blocks_count_ * block_size_);

  // type
  constexpr int EXT4_FEATURE_COMPAT_HAS_JOURNAL = 0x0004;
  constexpr int EXT4_FEATURE_INCOMPAT_EXTENTS = 0x0040;

  if (feature_incompat_ & EXT4_FEATURE_INCOMPAT_EXTENTS)
    set_fs_type ("ext4");

  else if (feature_compat_ & EXT4_FEATURE_COMPAT_HAS_JOURNAL)
    set_fs_type ("ext3");

  else
    set_fs_type ("ext2");

  // name
  std::string name = get_fs_type () + " (";

  if (!volume_name_.empty ())
    name += volume_name_;
  else
    name += "UUID: " + volume_uuid_;
  name += ')';

  set_name (name);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get metadata
//! \return metadata
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::metadata
filesystem_impl_ext2::get_metadata () const
{
  return mobius::metadata
  {
    {
      "type",
      "type",
      "std::string",
      get_type ()
    },
    {
      "offset",
      "offset in bytes",
      "std::uint64_t",
      std::to_string (get_offset ())
    },
    {
      "size",
      "size",
      "size_type",
      std::to_string (get_size ()) + " bytes"
    },
    {
      "name",
      "name",
      "std::string",
      get_name ()
    },
    {
      "fs_type",
      "filesystem type",
      "std::string",
      get_fs_type ()
    },
    {
      "volume_uuid",
      "volume UUID",
      "std::string",
      get_volume_uuid ()
    },
    {
      "volume_name",
      "volume name",
      "std::string",
      get_volume_name ()
    },
    {
      "creator_os",
      "creator operating system",
      "std::uint32_t",
      std::to_string (get_creator_os ())
    },
    {
      "inodes_count",
      "total inode count",
      "std::uint32_t",
      std::to_string (get_inodes_count ())
    },
    {
      "blocks_count",
      "total block count",
      "std::uint64_t",
      std::to_string (get_blocks_count ())
    },
    {
      "root_blocks_count",
      "number of blocks allocated by root",
      "std::uint64_t",
      std::to_string (get_root_blocks_count ())
    },
    {
      "free_blocks_count",
      "free blocks count",
      "std::uint64_t",
      std::to_string (get_free_blocks_count ())
    },
    {
      "free_inodes_count",
      "free inode count",
      "std::uint32_t",
      std::to_string (get_free_inodes_count ())
    },
    {
      "first_data_block",
      "first data block",
      "std::uint32_t",
      std::to_string (get_first_data_block ())
    },
    {
      "block_size",
      "block size",
      "std::uint64_t",
      std::to_string (get_block_size ()) + " bytes"
    },
    {
      "cluster_size",
      "cluster size",
      "std::uint64_t",
      std::to_string (get_cluster_size ()) + " bytes"
    },
    {
      "blocks_per_group",
      "blocks per group",
      "std::uint32_t",
      std::to_string (get_blocks_per_group ())
    },
    {
      "clusters_per_group",
      "clusters per group",
      "std::uint32_t",
      std::to_string (get_clusters_per_group ())
    },
    {
      "inodes_per_group",
      "inodes per group",
      "std::uint32_t",
      std::to_string (get_inodes_per_group ())
    },
    {
      "creation_time",
      "filesystem creation date/time",
      "mobius::datetime::datetime",
      to_string (get_creation_time ())
    },
    {
      "last_mount_time",
      "last mount date/time",
      "mobius::datetime::datetime",
      to_string (get_last_mount_time ())
    },
    {
      "last_write_time",
      "last write date/time",
      "mobius::datetime::datetime",
      to_string (get_last_write_time ())
    },
    {
      "last_check_time",
      "last check date/time",
      "mobius::datetime::datetime",
      to_string (get_last_check_time ())
    },
    {
      "first_error_time",
      "first error date/time",
      "mobius::datetime::datetime",
      to_string (get_first_error_time ())
    },
    {
      "last_error_time",
      "last error date/time",
      "mobius::datetime::datetime",
      to_string (get_last_error_time ())
    },
    {
      "mount_count",
      "number of mounts since the last fsck",
      "std::uint32_t",
      std::to_string (get_mount_count ())
    },
    {
      "max_mount_count",
      "number of mounts for mandatory fsck",
      "std::uint32_t",
      std::to_string (get_max_mount_count ())
    },
    {
      "state",
      "file system state",
      "std::uint16_t",
      std::to_string (get_state ())
    },
    {
      "errors",
      "action when detecting errors",
      "std::uint16_t",
      std::to_string (get_errors ())
    },
    {
      "error_count",
      "number of errors",
      "std::uint32_t",
      std::to_string (get_error_count ())
    },
    {
      "minor_revision_level",
      "minor revision level",
      "std::uint16_t",
      std::to_string (get_minor_revision_level ())
    },
    {
      "check_interval",
      "max check interval time",
      "std::uint32_t",
      std::to_string (get_check_interval ()) + " seconds"
    },
    {
      "revision_level",
      "revision level",
      "std::uint32_t",
      std::to_string (get_revision_level ())
    },
    {
      "def_resuid",
      "default uid for reserved blocks",
      "std::uint16_t",
      std::to_string (get_def_resuid ())
    },
    {
      "def_resgid",
      "default gid for reserved blocks",
      "std::uint16_t",
      std::to_string (get_def_resgid ())
    },
    {
      "first_inode",
      "first non-reserved inode",
      "std::uint32_t",
      std::to_string (get_first_inode ())
    },
    {
      "inode_size",
      "inode size",
      "std::uint16_t",
      std::to_string (get_inode_size ()) + " bytes"
    },
    {
      "block_group_number",
      "block group number of this superblock",
      "std::uint16_t",
      std::to_string (get_block_group_number ())
    },
    {
      "feature_compat",
      "compatible features flags",
      "std::uint32_t",
      std::to_string (get_feature_compat ())
    },
    {
      "feature_incompat",
      "incompatible features flags",
      "std::uint32_t",
      std::to_string (get_feature_incompat ())
    },
    {
      "feature_read_only",
      "readonly compatible features flags",
      "std::uint32_t",
      std::to_string (get_feature_read_only ())
    },
    {
      "last_mount_point",
      "last mounting point",
      "std::string",
      get_last_mount_point ()
    },
    {
      "journal_uuid",
      "journal superblock UUID",
      "std::string",
      get_journal_uuid ()
    },
    {
      "journal_inode",
      "journal inode number",
      "std::uint32_t",
      std::to_string (get_journal_inode ())
    },
    {
      "journal_device",
      "journal device number",
      "std::uint32_t",
      std::to_string (get_journal_device ())
    },
    {
      "default_hash_version",
      "default hash algorithm",
      "std::uint8_t",
      std::to_string (get_default_hash_version ())
    },
    {
      "default_mount_options",
      "default mount options",
      "std::uint32_t",
      std::to_string (get_default_mount_options ())
    },
    {
      "mount_options",
      "mount options",
      "std::string",
      get_mount_options ()
    },
    {
      "kib_written",
      "KiB written into this filesystem",
      "std::uint32_t",
      std::to_string (get_kib_written ())
    },
    {
      "checksum",
      "superblock checksum",
      "std::uint32_t",
      std::to_string (get_checksum ())
    },
  };
}

} // namespace filesystem
} // namespace mobius
