# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023 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/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import mobius
import pymobius.deprecated

LAST_VFS_KEY = None
LAST_VFS = None
EFISYS_MAX_SIZE = 204800 * 512

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief get item vfs
# @param item
# @return vfs or None
#
# This function is cached for the last case item. It supposes that users
# often browse at least a few extensions for a given item.
#
# The cache could be expanded, but as each VFS consumes resources such as
# file descriptors and memory, it should be minimized.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_item_vfs (item):
  global LAST_VFS_KEY
  global LAST_VFS
  v = None
  key = (item.case.uid, item.uid)

  if key == LAST_VFS_KEY and LAST_VFS.is_available ():
    v = LAST_VFS

  else:
    if item.has_vfs ():
      disk = pymobius.deprecated.get_disk_from_datasource (item.datasource)

      if disk and disk.is_available ():
        v = vfs (disk)
        LAST_VFS = v
        LAST_VFS_KEY = key

  return v

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Virtual filesystem implementation
# @author Eduardo Aguiar
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class vfs (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief initialize object
  # @param disk disk object
  # @param offsets offset list of deleted partitions addresses
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, disk, offsets=[]):
    self.__fs = []
    self.__disk = disk

    # scan filesystems
    filesystems = disk.get_filesystems () + [ mobius.vfs.filesystem (disk.new_reader (), offset) for offset in offsets ]

    # boot filesystem? (Win)
    if len (filesystems) > 1 and filesystems[0].type in ('ntfs', 'vfat'):
      root = filesystems[0].get_root_folder ()

      # @see https://en.wikipedia.org/wiki/EFI_system_partition
      if root.get_child_by_name ('Boot', False) and root.get_child_by_name ('bootmgr', False) and filesystems[0].size <= EFISYS_MAX_SIZE:
        self.__fs.append (('/EFISYS', filesystems[0]))
        del filesystems[0]

    # build filesystem list
    drive = 0

    for fs in filesystems:

      # win partitions?
      if fs.type in ('ntfs', 'vfat'):
        path = '%c:' % (drive + ord ('C'))

      # no
      else:
        path = '/part-%02d' % drive

      self.__fs.append ((path, fs))
      drive += 1

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief iterate through filesystems
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __iter__ (self):
    for path, fs in self.__fs:
      root = fs.get_root_folder ()
      root.path = path
      yield root

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Check if disk is still available
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def is_available (self):
    return self.__disk.is_available ()

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Get filesystems
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get_fs (self):
    return [ fs for path, fs in self.__fs ]
