Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dissect/fat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
from dissect.fat.fat import FATFS

__all__ = [
"ExFAT",
"FATFS",
"BadClusterError",
"EmptyDirectoryError",
"Error",
"ExFAT",
"FileNotFoundError",
"FreeClusterError",
"InvalidBPB",
Expand Down
2 changes: 2 additions & 0 deletions dissect/fat/c_exfat.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from dissect.cstruct import cstruct

exfat_def = """
Expand Down
2 changes: 2 additions & 0 deletions dissect/fat/c_fat.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from dissect.cstruct import cstruct

# https://ogris.de/fatrepair/fat.c
Expand Down
54 changes: 28 additions & 26 deletions dissect/fat/exfat.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import logging
import os
import struct
from collections import OrderedDict
from itertools import groupby
from operator import itemgetter
from typing import BinaryIO

from dissect.util.stream import RangeStream, RunlistStream

Expand All @@ -27,7 +30,7 @@


class ExFAT:
def __init__(self, fh):
def __init__(self, fh: BinaryIO):
self.filesystem = fh
fh.seek(0)

Expand Down Expand Up @@ -56,7 +59,7 @@
self.upcase_table = self.root_directory.upcase_entry
self.volume_label = self.root_directory.volume_entry.volume_label.strip("\x00")

def cluster_to_sector(self, cluster):
def cluster_to_sector(self, cluster: int) -> int | None:
"""
Returns the clusters' corresponding sector address

Expand All @@ -70,7 +73,7 @@
sector = ((cluster - 2) * (2**self.vbr.sectors_per_cluster_exp)) + self.cluster_heap_sector
return sector if sector > 0 else None

def sector_to_cluster(self, sector):
def sector_to_cluster(self, sector: int) -> int | None:
"""
Returns the sectors' corresponding cluster address

Expand All @@ -84,7 +87,9 @@
cluster = ((sector - self.cluster_to_sector(2)) // self.sector_size) + 2
return cluster if cluster >= 2 else None

def runlist(self, starting_cluster, not_fragmented=True, size=None):
def runlist(
self, starting_cluster: int, not_fragmented: bool = True, size: int | None = None
) -> list[tuple[int, int]]:
"""
Creates a RunlistStream compatible runlist from exFAT FAT structures, in sectors.

Expand Down Expand Up @@ -120,7 +125,7 @@

return runlist

def get_cluster_chain(self, starting_cluster):
def get_cluster_chain(self, starting_cluster: int) -> list[int]:
"""
Reads the on disk FAT to construct the cluster chain

Expand All @@ -136,17 +141,17 @@

if starting_cluster < 2:
return chain
else:
while next_ != EOC:
self.fat.seek(starting_cluster * FAT_ENTRY_SIZE)
next_ = struct.unpack("<L", self.fat.read(FAT_ENTRY_SIZE))[0]
chain.append(starting_cluster)
starting_cluster = next_

return chain
while next_ != EOC:
self.fat.seek(starting_cluster * FAT_ENTRY_SIZE)
next_ = struct.unpack("<L", self.fat.read(FAT_ENTRY_SIZE))[0]
chain.append(starting_cluster)
starting_cluster = next_

return chain

@staticmethod
def _utc_timezone(timezone):
def _utc_timezone(timezone: int) -> dict[str, str | int]:
"""
Converts a Microsoft exFAT timezone byte to its UTC timezone equivalent

Expand All @@ -173,12 +178,12 @@
utc_name = f"UTC{hours:+03}:{minutes:02}"

return {"name": utc_name, "offset": utc_minute_offset}
else:
return {"name": "localtime", "offset": 0}

return {"name": "localtime", "offset": 0}

Check warning on line 182 in dissect/fat/exfat.py

View check run for this annotation

Codecov / codecov/patch

dissect/fat/exfat.py#L182

Added line #L182 was not covered by tests


class RootDirectory:
def __init__(self, fh, location, exfat):
def __init__(self, fh: BinaryIO, location: int, exfat: ExFAT):
self.exfat = exfat
self.location = location
self.size = 0
Expand All @@ -189,7 +194,7 @@
self.dict = OrderedDict()
self._parse_root_dir(fh)

def _parse_root_dir(self, fh):
def _parse_root_dir(self, fh: BinaryIO) -> None:
"""
Parses the passed fh to construct the Root directory object"""

Expand All @@ -206,7 +211,7 @@
self.root_dir = RunlistStream(fh, runlist, self.size, self.exfat.sector_size)
self.dict = self._create_root_dir(self.root_dir)

def _parse_subdir(self, entry):
def _parse_subdir(self, entry: c_exfat.FILE) -> OrderedDict:
"""
Parses the given sub directory file directory entry for containing files

Expand All @@ -225,7 +230,7 @@
return self._parse_file_entries(fh)

@staticmethod
def _construct_filename(fn_entries, is_dir=False):
def _construct_filename(fn_entries: list[c_exfat.FILENAME_DIRECTORY_ENTRY], is_dir: bool = False) -> str:
"""
Assembles the filename from given file name directory entries

Expand All @@ -247,7 +252,7 @@

return filename if not is_dir else filename + "/"

def _parse_file_entries(self, fh):
def _parse_file_entries(self, fh: BinaryIO) -> OrderedDict:
"""
Finds and parses file entries in a given file handle (file like object)

Expand All @@ -271,10 +276,7 @@
fnentry_count = metadata.subentry_count - 1

stream = c_exfat.STREAM_DIRECTORY_ENTRY(fh.read(DIR_ENTRY_SIZE))
fn_entries = []

for _ in range(fnentry_count):
fn_entries.append(c_exfat.FILENAME_DIRECTORY_ENTRY(fh.read(DIR_ENTRY_SIZE)))
fn_entries = [c_exfat.FILENAME_DIRECTORY_ENTRY(fh.read(DIR_ENTRY_SIZE)) for _ in range(fnentry_count)]

file_ = c_exfat.FILE(metadata=metadata, stream=stream, fn_entries=fn_entries)
if file_.metadata.attributes.directory:
Expand All @@ -294,15 +296,15 @@

return entries

def _non_file_entries(self, entry):
def _non_file_entries(self, entry: c_exfat.FILE_DIRECTORY_ENTRY) -> None:
if entry.entry_type == VOLUME_LABEL_ENTRY or entry.entry_type == NO_VOLUME_LABEL_ENTRY:
self.volume_entry = c_exfat.VOLUME_DIRECTORY_ENTRY(entry.dumps())
elif entry.entry_type == BITMAP_ENTRY:
self.bitmap_entry = c_exfat.BITMAP_DIRECTORY_ENTRY(entry.dumps())
elif entry.entry_type == UPCASE_TABLE_ENTRY:
self.upcase_entry = c_exfat.UPCASE_DIRECTORY_ENTRY(entry.dumps())

def _create_root_dir(self, root_dir):
def _create_root_dir(self, root_dir: BinaryIO) -> OrderedDict:
"""
Since exFAT does not have a dedicated root directory entry
one has to be constructed form available parameters during filesystem parsing.
Expand Down
Loading
Loading