コード例 #1
0
ファイル: PyFat.py プロジェクト: matthewfcarlson/pyfat
    def get_cluster_chain(self, first_cluster):
        """Follow a cluster chain beginning with the first cluster address."""
        i = first_cluster
        while i <= len(self.fat):
            if self.FAT_CLUSTER_VALUES[self.fat_type]["MIN_DATA_CLUSTER"] <= self.fat[i] <= self.FAT_CLUSTER_VALUES[self.fat_type]["MAX_DATA_CLUSTER"]:
                # Normal data cluster, follow chain
                yield i
            elif self.fat_type == self.FAT_TYPE_FAT12 and self.fat[i] == self.FAT12_SPECIAL_EOC:
                # Special EOC
                yield i
                return
            elif self.FAT_CLUSTER_VALUES[self.fat_type]["END_OF_CLUSTER_MIN"] <= self.fat[i] <= self.FAT_CLUSTER_VALUES[self.fat_type]["END_OF_CLUSTER_MAX"]:
                # End of cluster, end chain
                yield i
                return
            elif self.fat[i] == self.FAT_CLUSTER_VALUES[self.fat_type]["BAD_CLUSTER"]:
                # Bad cluster, cannot follow chain, file broken!
                raise PyFATException("Bad cluster found in FAT cluster "
                                     "chain, cannot access file")
            elif self.fat[i] == self.FAT_CLUSTER_VALUES[self.fat_type]["FREE_CLUSTER"]:
                # FREE_CLUSTER mark when following a chain is treated as an error
                raise PyFATException("FREE_CLUSTER mark found in FAT cluster "
                                     "chain, cannot access file")
            else:
                raise PyFATException("Invalid or unknown FAT cluster "
                                     "entry found with value "
                                     "\'{}\'".format(hex(self.fat[i])))

            i = self.fat[i]
コード例 #2
0
ファイル: PyFat.py プロジェクト: matthewfcarlson/pyfat
    def parse_header(self):
        with self.__lock:
            self.__seek(0)
            boot_sector = self.__fp.read(512)

        header = struct.unpack(self.bpb_header_layout, boot_sector[:36])
        self.bpb_header = dict(zip(self.bpb_header_vars, header))

        # Verify BPB headers
        self.__verify_bpb_header()

        # Calculate number of root directory sectors and starting point of root directory
        self.root_dir_sectors = ((self.bpb_header["BPB_RootEntCnt"] * FATDirectoryEntry.FAT_DIRECTORY_HEADER_SIZE) + (self.bpb_header["BPB_BytsPerSec"] - 1)) // self.bpb_header["BPB_BytsPerSec"]
        self.root_dir_sector = self.bpb_header["BPB_RsvdSecCnt"] + (self._get_fat_size_count() * self.bpb_header["BPB_NumFATS"])

        # Calculate first data sector
        self.first_data_sector = self.bpb_header["BPB_RsvdSecCnt"] + (self.bpb_header["BPB_NumFATS"] * self._get_fat_size_count()) + self.root_dir_sectors

        # Determine FAT type
        self.fat_type = self.__determine_fat_type()

        # Parse FAT type specific header
        if self.fat_type in [self.FAT_TYPE_FAT12, self.FAT_TYPE_FAT16]:
            self.__parse_fat12_header()

            self.__verify_fat12_header()
        elif self.fat_type == self.FAT_TYPE_FAT32:
            # FAT32, probably - probe for it
            if self.bpb_header["BPB_FATSz16"] != 0:
                raise PyFATException(f"Invalid BPB_FATSz16 value of "
                                     f"'{self.bpb_header['BPB_FATSz16']}', "
                                     f"filesystem corrupt?",
                                     errno=errno.EINVAL)

            self.__parse_fat32_header()

            # TODO: Verify FAT32 header
        else:
            raise PyFATException("Unknown FAT filesystem type, "
                                 "filesystem corrupt?", errno=errno.EINVAL)

        # Check signature
        with self.__lock:
            self.__seek(510)
            signature = struct.unpack("<H", self.__fp.read(2))[0]

        if signature != 0xAA55:
            raise PyFATException(f"Invalid signature: \'{hex(signature)}\'.")

        # Initialisation finished
        self.initialised = True
コード例 #3
0
    def _add_parent(self, cls):
        """Add parent directory link to current directory entry.

        raises: PyFATException
        """
        if self._parent is not None:
            raise PyFATException("Trying to add multiple parents to current "
                                 "directory!", errno=errno.ETOOMANYREFS)

        if not isinstance(cls, FATDirectoryEntry):
            raise PyFATException("Trying to add a non-FAT directory entry "
                                 "as parent directory!", errno=errno.EBADE)

        self._parent = cls
コード例 #4
0
    def __verify_bpb_header(self):
        """Verify BPB header for correctness."""
        if self.bpb_header["BS_jmpBoot"][0] == 0xEB:
            if self.bpb_header["BS_jmpBoot"][2] != 0x90:
                raise PyFATException("Boot code must end with 0x90")
        elif self.bpb_header["BS_jmpBoot"][0] == 0xE9:
            pass
        else:
            raise PyFATException(
                "Boot code must start with 0xEB or 0xE9. Is this a FAT partition?"
            )

        if self.bpb_header["BPB_BytsPerSec"] not in [
                2**x for x in range(9, 13)
        ]:
            raise PyFATException(
                "Expected one of {} bytes per sector, got: "
                "\'{}\'.".format([2**x for x in range(9, 13)],
                                 self.bpb_header["BPB_BytsPerSec"]))

        if self.bpb_header["BPB_SecPerClus"] not in [2**x for x in range(8)]:
            raise PyFATException(
                "Expected one of {} sectors per cluster, got"
                ": \'{}\'.".format([2**x for x in range(8)],
                                   self.bpb_header["BPB_SecPerClus"]))

        bytes_per_cluster = self.bpb_header[
            "BPB_BytsPerSec"] * self.bpb_header["BPB_SecPerClus"]
        if bytes_per_cluster > 32768:
            warnings.warn(
                "Bytes per cluster should not be more than 32K, "
                "but got: {}K. Trying to continue "
                "anyway.".format(bytes_per_cluster // 1024), Warning)

        if self.bpb_header["BPB_RsvdSecCnt"] == 0:
            raise PyFATException("Number of reserved sectors must not be 0")

        if self.bpb_header["BPB_Media"] not in [
                0xf0, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
        ]:
            raise PyFATException("Invalid media type")

        if self.bpb_header["BPB_NumFATS"] < 1:
            raise PyFATException("At least one FAT expected, None found.")

        root_entry_count = (self.bpb_header["BPB_RootEntCnt"] *
                            32) % self.bpb_header["BPB_BytsPerSec"]
        if self.bpb_header["BPB_RootEntCnt"] != 0 and root_entry_count != 0:
            raise PyFATException("Root entry count does not cleanly align with"
                                 " bytes per sector!")

        if self.bpb_header["BPB_TotSec16"] == 0 and self.bpb_header[
                "BPB_TotSec32"] == 0:
            raise PyFATException("16-Bit and 32-Bit total sector count "
                                 "value empty.")
コード例 #5
0
    def get_parent_dir(self):
        """Returns the parent directory entry."""
        if self._parent is None:
            raise PyFATException("Cannot query parent directory of "
                                 "root directory", errno=errno.ENOENT)

        return self._parent
コード例 #6
0
    def open(self, filename: str, read_only: bool = False):
        """Open filesystem for usage with PyFat.

        :param filename: `str`: Name of file to open for usage with PyFat.
        :param read_only: `bool`: Force read-only mode of filesystem.
        """
        self.is_read_only = read_only
        if read_only is True:
            mode = 'rb'
        else:
            mode = 'rb+'

        try:
            self.__set_fp(open(filename, mode=mode))
        except OSError as ex:
            raise PyFATException(f"Cannot open given file \'{filename}\'.",
                                 errno=ex.errno)

        # Parse BPB & FAT headers of given file
        self.parse_header()

        # Parse FAT
        self._parse_fat()

        # Parse root directory
        # TODO: Inefficient to always recursively parse the root dir.
        #       It would make sense to parse it on demand instead.
        self.parse_root_dir()
コード例 #7
0
ファイル: PyFat.py プロジェクト: matthewfcarlson/pyfat
    def __verify_bpb_header(self):
        """Verify BPB header for correctness."""
        if self.bpb_header["BS_jmpBoot"][0] == 0xEB:
            if self.bpb_header["BS_jmpBoot"][2] != 0x90:
                raise PyFATException("Boot code must end with 0x90")
        elif self.bpb_header["BS_jmpBoot"][0] == 0xE9:
            pass
        else:
            raise PyFATException("Boot code must start with 0xEB or "
                                 "0xE9. Is this a FAT partition?")

        #: 512,1024,2048,4096: As per fatgen103.doc
        byts_per_sec_range = [2**x for x in range(9, 13)]
        if self.bpb_header["BPB_BytsPerSec"] not in byts_per_sec_range:
            raise PyFATException(f"Expected one of {byts_per_sec_range} "
                                 f"bytes per sector, got: "
                                 f"\'{self.bpb_header['BPB_BytsPerSec']}\'.")

        #: 1,2,4,8,16,32,64,128: As per fatgen103.doc
        sec_per_clus_range = [2**x for x in range(8)]
        if self.bpb_header["BPB_SecPerClus"] not in sec_per_clus_range:
            raise PyFATException(f"Expected one of {sec_per_clus_range} "
                                 f"sectors per cluster, got: "
                                 f"\'{self.bpb_header['BPB_SecPerClus']}\'.")

        bytes_per_cluster = self.bpb_header["BPB_BytsPerSec"]
        bytes_per_cluster *= self.bpb_header["BPB_SecPerClus"]
        if bytes_per_cluster > 32768:
            warnings.warn("Bytes per cluster should not be more than 32K, "
                          "but got: {}K. Trying to continue "
                          "anyway.".format(bytes_per_cluster // 1024), Warning)

        if self.bpb_header["BPB_RsvdSecCnt"] == 0:
            raise PyFATException("Number of reserved sectors must not be 0")

        if self.bpb_header["BPB_Media"] not in [0xf0, 0xf8, 0xf9, 0xfa, 0xfb,
                                                0xfc, 0xfd, 0xfe, 0xff]:
            raise PyFATException("Invalid media type")

        if self.bpb_header["BPB_NumFATS"] < 1:
            raise PyFATException("At least one FAT expected, None found.")

        root_entry_count = self.bpb_header["BPB_RootEntCnt"] * 32
        root_entry_count %= self.bpb_header["BPB_BytsPerSec"]
        if self.bpb_header["BPB_RootEntCnt"] != 0 and root_entry_count != 0:
            raise PyFATException("Root entry count does not cleanly align with"
                                 " bytes per sector!")

        if self.bpb_header["BPB_TotSec16"] == 0 and \
                self.bpb_header["BPB_TotSec32"] == 0:
            raise PyFATException("16-Bit and 32-Bit total sector count "
                                 "value empty.")
コード例 #8
0
    def _wrapper(*args, **kwargs):
        initialised = args[0].initialised

        if initialised is True:
            return func(*args, **kwargs)
        else:
            raise PyFATException("Class has not yet been fully initialised, "
                                 "please instantiate first.")
コード例 #9
0
    def _wrapper(*args, **kwargs):
        read_only = args[0].is_read_only

        if read_only is False:
            return func(*args, **kwargs)
        else:
            raise PyFATException("Filesystem has been opened read-only, not "
                                 "able to perform a write operation!")
コード例 #10
0
    def allocate_bytes(self, size: int, erase: bool = False):
        """Try to allocate a cluster (-chain) in FAT for `size` bytes.

        :param size: `int`: Size in bytes to try to allocate.
        :param erase: `bool`: If set to true, the newly allocated
                              space is zeroed-out for clean allocation.
        :returns: List of newly-allocated clusters.
        """

        # Calculate number of clusters required for file
        num_clusters = size / self.bytes_per_cluster
        num_clusters = math.ceil(num_clusters)

        # Fill list of found free clusters
        free_clusters = []
        for i, _ in enumerate(self.fat):
            if self.FAT_CLUSTER_VALUES[self.fat_type][
                    "MIN_DATA_CLUSTER"] > i > self.FAT_CLUSTER_VALUES[
                        self.fat_type]["MAX_DATA_CLUSTER"]:
                # Ignore out of bound entries
                continue

            if num_clusters == len(free_clusters):
                # Allocated enough clusters!
                break

            if self.fat[i] == self.FAT_CLUSTER_VALUES[
                    self.fat_type]["FREE_CLUSTER"]:
                if i == self.FAT_CLUSTER_VALUES[self.fat_type]["BAD_CLUSTER"]:
                    # Do not allocate a BAD_CLUSTER
                    continue

                if self.fat_type == self.FAT_TYPE_FAT12 and i == self.FAT12_SPECIAL_EOC:
                    # Do not allocate special EOC marker on FAT12
                    continue

                free_clusters += [i]
        else:
            free_space = len(free_clusters) * self.bytes_per_cluster
            raise PyFATException(
                f"Not enough free space to allocate "
                f"{size} bytes ({free_space} bytes free)",
                errno=errno.ENOSPC)

        # Allocate cluster chain in FAT
        for i, _ in enumerate(free_clusters):
            try:
                self.fat[free_clusters[i]] = free_clusters[i + 1]
            except IndexError:
                self.fat[free_clusters[i]] = self.FAT_CLUSTER_VALUES[
                    self.fat_type]["END_OF_CLUSTER_MAX"]

            if erase is True:
                with self.__lock:
                    self.__seek(self.get_data_cluster_address(i))
                    self.__fp.write(b'\0' * self.bytes_per_cluster)

        return free_clusters
コード例 #11
0
    def make_8dot3_name(dir_name: str, parent_dir_entry) -> str:
        """Generate filename based on 8.3 rules out of a long file name.

        In 8.3 notation we try to use the first 6 characters and
        fill the rest with a tilde, followed by a number (starting
        at 1). If that entry is already given, we increment this
        number and try again until all possibilities are exhausted
        (i.e. A~999999.TXT).

        :param dir_name: Long name of directory entry.
        :param parent_dir_entry: `FATDirectoryEntry`: Dir entry of parent dir.
        :returns: `str`: 8DOT3 compliant filename.
        :raises: PyFATException: If parent dir is not a directory
                                 or all name generation possibilities
                                 are exhausted
        """
        dirs, files, _ = parent_dir_entry.get_entries()
        dir_entries = [e.get_short_name() for e in dirs + files]

        extsep = "."

        try:
            # Shorten to 8 chars; strip invalid characters
            basename = os.path.splitext(dir_name)[0][0:8].upper().strip()
            basename = ''.join(
                filter(lambda x: x in EightDotThree.VALID_CHARACTERS,
                       basename))
        except IndexError:
            basename = ""

        try:
            # Shorten to 3 chars; strip invalid characters
            extname = os.path.splitext(dir_name)[1][1:4].upper().strip()
            extname = ''.join(
                filter(lambda x: x in EightDotThree.VALID_CHARACTERS, extname))
        except IndexError:
            extname = ""

        if len(extname) == 0:
            extsep = ""

        # Loop until suiting name is found
        i = 0
        while len(str(i)) + 1 <= 7:
            if i > 0:
                maxlen = 8 - (1 + len(str(i)))
                basename = f"{basename[0:maxlen]}~{i}"

            short_name = f"{basename}{extsep}{extname}"

            if short_name not in dir_entries:
                return short_name
            i += 1

        raise PyFATException(
            "Cannot generate 8dot3 filename, "
            "unable to find suiting short file name.",
            errno=errno.EEXIST)
コード例 #12
0
    def _verify_is_directory(self):
        """Verify that current entry is a directory.

        raises: PyFATException: If current entry is not a directory.
        """
        if not self.is_directory():
            raise PyFATException("Cannot get entries of this entry, as "
                                 "it is not a directory.",
                                 errno=errno.ENOTDIR)
コード例 #13
0
    def _get_fat_size_count(self):
        if self.bpb_header["BPB_FATSz16"] != 0:
            return self.bpb_header["BPB_FATSz16"]

        # Only possible with FAT32
        self.__parse_fat32_header()
        try:
            return self.fat_header["BPB_FATSz32"]
        except KeyError:
            raise PyFATException("Invalid FAT size of 0 detected in header, "
                                 "cannot continue")
コード例 #14
0
    def __verify_fat12_header(self):
        """Verify FAT12/16 header for correctness."""
        if self.fat_type == self.FAT_TYPE_FAT12 and self.fat_header[
                "BS_FilSysType"] not in [
                    self.FS_TYPES[self.FAT_TYPE_UNKNOWN],
                    self.FS_TYPES[self.FAT_TYPE_FAT12]
                ]:
            raise PyFATException("Invalid filesystem type \'{}\' "
                                 "for FAT12".format(self.fat_type))
        elif self.fat_type == self.FAT_TYPE_FAT16 and self.fat_header[
                "BS_FilSysType"] not in [
                    self.FS_TYPES[self.FAT_TYPE_UNKNOWN],
                    self.FS_TYPES[self.FAT_TYPE_FAT16]
                ]:
            raise PyFATException("Invalid filesystem type \'{}\' "
                                 "for FAT16".format(self.fat_type))

        if self.fat_header["BPB_DrvNum"] not in [0x00, 0x80]:
            raise PyFATException("Invalid drive number \'"
                                 "{}\'".format(self.fat_header["BPB_DrvNum"]))
コード例 #15
0
ファイル: FATDirectoryEntry.py プロジェクト: akx/pyfat
    def add_lfn_entry(self, LDIR_Ord, LDIR_Name1, LDIR_Attr, LDIR_Type,
                      LDIR_Chksum, LDIR_Name2, LDIR_FstClusLO, LDIR_Name3):
        """Add LFN entry to this instances chain.

        :param LDIR_Ord: Ordinance of LFN entry
        :param LDIR_Name1: First name field of LFN entry
        :param LDIR_Attr: Attributes of LFN entry
        :param LDIR_Type: Type of LFN entry
        :param LDIR_Chksum: Checksum value of following 8dot3 entry
        :param LDIR_Name2: Second name field of LFN entry
        :param LDIR_FstClusLO: Cluster address of LFN entry. Always zero.
        :param LDIR_Name3: Third name field of LFN entry
        """
        # Check if attribute matches
        if not self.is_lfn_entry(LDIR_Ord, LDIR_Attr):
            raise NotAnLFNEntryException("Given LFN entry is not a long "
                                         "file name entry or attribute "
                                         "not set correctly!")

        # Check if FstClusLO is 0, as required by the spec
        if LDIR_FstClusLO != 0:
            raise PyFATException(
                "Given LFN entry has an invalid first "
                "cluster ID, don't know what to do.",
                errno=errno.EFAULT)

        # Check if item with same index has already been added
        if LDIR_Ord in self.lfn_entries.keys():
            raise PyFATException("Given LFN entry part with index \'{}\'"
                                 "has already been added to LFN "
                                 "entry list.".format(LDIR_Ord))

        mapped_entries = dict(
            zip(self.FAT_LONG_DIRECTORY_VARS,
                (LDIR_Ord, LDIR_Name1, LDIR_Attr, LDIR_Type, LDIR_Chksum,
                 LDIR_Name2, LDIR_FstClusLO, LDIR_Name3)))

        self.lfn_entries[LDIR_Ord] = mapped_entries
コード例 #16
0
    def write_data_to_cluster(self,
                              data: bytes,
                              cluster: int,
                              extend_cluster: bool = True,
                              erase: bool = False) -> None:
        """Write given data to cluster.

        Extends cluster chain if needed.

        :param data: `bytes`: Data to write to cluster
        :param cluster: `int`: Cluster to write data to.
        :param extend_cluster: `bool`: Automatically extend cluster chain
                               if not enough space is available.
        :param erase: `bool`: Erase cluster contents before writing.
                      This is useful when writing `FATDirectoryEntry` data.
        """
        data_sz = len(data)
        cluster_sz = 0
        last_cluster = None
        for c in self.get_cluster_chain(cluster):
            cluster_sz += self.bytes_per_cluster
            last_cluster = c

        if data_sz > cluster_sz:
            if extend_cluster is False:
                raise PyFATException(
                    "Cannot write data to cluster, "
                    "not enough space available.",
                    errno=errno.ENOSPC)

            new_chain = self.allocate_bytes(data_sz - cluster_sz,
                                            erase=erase)[0]
            self.fat[last_cluster] = new_chain

        # Fill rest of data with zeroes if erase is set to True
        if erase:
            new_sz = self.bytes_per_cluster - data_sz
            new_sz %= self.bytes_per_cluster
            new_sz += data_sz
            data += b'\0' * (new_sz - data_sz)

        # Write actual data
        bytes_written = 0
        for c in self.get_cluster_chain(cluster):
            b = self.get_data_cluster_address(c)
            t = bytes_written
            bytes_written += self.bytes_per_cluster
            self._write_data_to_address(data[t:bytes_written], b)
コード例 #17
0
ファイル: PyFat.py プロジェクト: matthewfcarlson/pyfat
 def __verify_fat12_header(self):
     """Verify FAT12/16 header for correctness."""
     # Extended boot signature had two different versions:
     # 0x00: Pre-DOS 4.0 BPB (no EBPB present)
     # 0x28: Only BS_VolID is present
     # 0x29: Full FAT12 bootsector present
     bs_bootsig = self.fat_header["BS_BootSig"]
     if bs_bootsig == 0x29:
         bs_filsystype = self.fat_header["BS_FilSysType"]
         if bs_filsystype not in (self.FS_TYPES[self.FAT_TYPE_UNKNOWN],
                                  self.FS_TYPES[self.fat_type]):
             print(f"BS_FilSysType contains {bs_filsystype}, unexpected "
                   f"for FAT{self.fat_type}")
     elif bs_bootsig not in (0x28, 0x00, 0x2a):
         raise PyFATException("Invalid FAT extended boot signature value "
                              "encountered: '{}'".format(hex(bs_bootsig)))
コード例 #18
0
    def update_directory_entry(self, dir_entry: FATDirectoryEntry) -> None:
        """Update directory entry on disk.

        Special handling is required, since the root directory
        on FAT12/16 is on a fixed location on disk.

        :param dir_entry: `FATDirectoryEntry`: Directory to write to disk
        """
        is_root_dir = False
        extend_cluster_chain = True
        if self.root_dir == dir_entry:
            if self.fat_type != self.FAT_TYPE_FAT32:
                # FAT12/16 doesn't have a root directory cluster,
                # which cannot be enhanced
                extend_cluster_chain = False
            is_root_dir = True

        # Gather all directory entries
        dir_entries = b''
        d, f, s = dir_entry.get_entries()
        for d in list(itertools.chain(d, f, s)):
            dir_entries += d.byte_repr()

        # Write content
        if not is_root_dir or self.fat_type == self.FAT_TYPE_FAT32:
            # FAT32 and non-root dir entries can be handled normally
            self.write_data_to_cluster(dir_entries,
                                       dir_entry.get_cluster(),
                                       extend_cluster=extend_cluster_chain,
                                       erase=True)
        else:
            # FAT12/16 does not have a root directory cluster
            root_dir_addr = self.root_dir_sector * self.bpb_header[
                "BPB_BytsPerSec"]
            root_dir_sz = self.root_dir_sectors * self.bpb_header[
                "BPB_BytsPerSec"]

            if len(dir_entries) > root_dir_sz:
                raise PyFATException(
                    "Cannot create directory, maximum number "
                    "of root directory entries exhausted!",
                    errno=errno.ENOSPC)

            # Overwrite empty space as well
            dir_entries += b'\0' * (root_dir_sz - len(dir_entries))
            self._write_data_to_address(dir_entries, root_dir_addr)
コード例 #19
0
ファイル: FATDirectoryEntry.py プロジェクト: akx/pyfat
    def _search_entry(self, name: str):
        """Find given dir entry by walking current dir.

        :param name: Name of entry to search for
        :raises: PyFATException: If entry cannot be found
        :returns: FATDirectoryEntry: Found entry
        """
        dirs, files, _ = self.get_entries()
        for entry in dirs + files:
            try:
                if entry.get_long_name() == name:
                    return entry
            except NotAnLFNEntryException:
                pass
            if entry.get_short_name() == name:
                return entry

        raise PyFATException(f'Cannot find entry {name}', errno=errno.ENOENT)
コード例 #20
0
ファイル: FATDirectoryEntry.py プロジェクト: akx/pyfat
    def remove_subdirectory(self, name):
        """Remove given dir_entry from dir list."""
        # Check if current dir entry is even a directory!
        self._verify_is_directory()

        # Iterate all entries
        for dir_entry in self.__dirs:
            sn = dir_entry.get_short_name()
            try:
                ln = dir_entry.get_long_name()
            except NotAnLFNEntryException:
                ln = None
            if name in [sn, ln]:
                self.__dirs.remove(dir_entry)
                return

        raise PyFATException(
            f"Cannot remove '{name}', no such "
            f"file or directory!",
            errno=errno.ENOENT)
コード例 #21
0
    def remove_dir_entry(self, name):
        """Remove given dir_entry from dir list.

        **NOTE:** This will also remove special entries such
        as ».«, »..« and volume labels!

        """
        d, f, s = self.get_entries()

        # Iterate all entries
        for dir_entry in d + f + s:
            sn = dir_entry.get_short_name()
            try:
                ln = dir_entry.get_long_name()
            except NotAnLFNEntryException:
                ln = None
            if name in [sn, ln]:
                self.__dirs.remove(dir_entry)
                return

        raise PyFATException(f"Cannot remove '{name}', no such "
                             f"file or directory!", errno=errno.ENOENT)
コード例 #22
0
def make_lfn_entry(dir_name: str,
                   short_name):
    """Generate a `FATLongDirectoryEntry` instance from directory name.

    :param dir_name: Long name of directory
    :param short_name: `EightDotThree` class instance
    :raises PyFATException if entry name does not require an LFN
            entry or the name exceeds the FAT limitation of 255 characters
    """
    lfn_entry = FATLongDirectoryEntry()
    #: Length in bytes of an LFN entry
    lfn_entry_length = 26
    dir_name_str = dir_name
    dir_name = dir_name.encode(FAT_LFN_ENCODING)
    dir_name_modulus = len(dir_name) % lfn_entry_length

    if EightDotThree.is_8dot3_conform(dir_name_str):
        raise PyFATException("Directory entry is already 8.3 conform, "
                             "no need to create an LFN entry.",
                             errno=errno.EINVAL)

    if len(dir_name) > 255:
        raise PyFATException("Long file name exceeds 255 "
                             "characters, not supported.",
                             errno=errno.ENAMETOOLONG)

    checksum = short_name.checksum()

    if dir_name_modulus != 0:
        # Null-terminate string if required
        dir_name += '\0'.encode(FAT_LFN_ENCODING)

    # Fill the rest with 0xFF if it doesn't fit evenly
    new_sz = lfn_entry_length - len(dir_name)
    new_sz %= lfn_entry_length
    new_sz += len(dir_name)
    dir_name += b'\xFF' * (new_sz - len(dir_name))

    # Generate linked LFN entries
    lfn_entries = len(dir_name) // lfn_entry_length
    for i in range(lfn_entries):
        if i == lfn_entries-1:
            lfn_entry_ord = 0x40 | i+1
        else:
            lfn_entry_ord = i+1

        n = i*lfn_entry_length
        dirname1 = dir_name[n:n+10]
        n += 10
        dirname2 = dir_name[n:n+12]
        n += 12
        dirname3 = dir_name[n:n+4]

        lfn_entry.add_lfn_entry(LDIR_Ord=lfn_entry_ord,
                                LDIR_Name1=dirname1,
                                LDIR_Attr=FATDirectoryEntry.ATTR_LONG_NAME,
                                LDIR_Type=0x00,
                                LDIR_Chksum=checksum,
                                LDIR_Name2=dirname2,
                                LDIR_FstClusLO=0,
                                LDIR_Name3=dirname3)
    return lfn_entry
コード例 #23
0
 def __raise_8dot3_nonconformant(name: str):
     raise PyFATException(
         f"Given directory name "
         f"{name} is not conform "
         f"to 8.3 file naming convention.",
         errno=errno.EINVAL)
コード例 #24
0
 def __set_fp(self, fp):
     if isinstance(self.__fp, BufferedReader):
         raise PyFATException("Cannot overwrite existing file handle, "
                              "create new class instance of PyFAT.")
     self.__fp = fp
コード例 #25
0
 def __seek(self, address: int):
     """Seek to given address with offset."""
     if self.__fp is None:
         raise PyFATException("Cannot seek without a file handle!",
                              errno=errno.ENXIO)
     self.__fp.seek(address + self.__fp_offset)
コード例 #26
0
    def _parse_fat(self):
        """Parse information in FAT."""
        # Read all FATs
        fat_size = self.bpb_header[
            "BPB_BytsPerSec"] * self._get_fat_size_count()

        # Seek FAT entries
        first_fat_bytes = self.bpb_header["BPB_RsvdSecCnt"] * self.bpb_header[
            "BPB_BytsPerSec"]
        fats = []
        for i in range(self.bpb_header["BPB_NumFATS"]):
            with self.__lock:
                self.__seek(first_fat_bytes + (i * fat_size))
                fats += [self.__fp.read(fat_size)]

        if len(fats) < 1:
            raise PyFATException("Invalid number of FATs configured, "
                                 "cannot continue")
        elif len(set(fats)) > 1:
            raise PyFATException("One or more FATs differ, filesystem most "
                                 "likely corrupted")

        # Parse first FAT
        self.bytes_per_cluster = self.bpb_header["BPB_BytsPerSec"] * \
            self.bpb_header["BPB_SecPerClus"]

        if len(fats[0]) != self.bpb_header["BPB_BytsPerSec"] * \
                self._get_fat_size_count():
            raise PyFATException("Invalid length of FAT")

        # FAT12: 12 bits (1.5 bytes) per FAT entry
        # FAT16: 16 bits (2 bytes) per FAT entry
        # FAT32: 32 bits (4 bytes) per FAT entry
        fat_entry_size = self.fat_type / 8
        total_entries = fat_size // int(fat_entry_size)
        self.fat = [None] * total_entries

        curr = 0
        cluster = 0
        incr = -(-self.fat_type // 8)
        while curr < fat_size:
            offset = int(curr + incr)

            if self.fat_type == self.FAT_TYPE_FAT12:
                if curr == (fat_size - 1):
                    # Sector boundary case for FAT12
                    del self.fat[-1]
                    break

                self.fat[cluster] = struct.unpack("<H",
                                                  fats[0][curr:offset])[0]
                if curr % 2 == 0:
                    # Even: Only fetch low 12 bits
                    self.fat[cluster] &= 0x0FFF
                else:
                    # Odd: Only fetch high 12 bits
                    self.fat[cluster] >>= 4
            elif self.fat_type == self.FAT_TYPE_FAT16:
                self.fat[cluster] = struct.unpack("<H",
                                                  fats[0][curr:offset])[0]
            elif self.fat_type == self.FAT_TYPE_FAT32:
                self.fat[cluster] = struct.unpack("<L",
                                                  fats[0][curr:offset])[0]
                # Ignore first four bits, FAT32 clusters are
                # actually just 28bits long
                self.fat[cluster] &= 0x0FFFFFFF
            else:
                raise PyFATException("Unknown FAT type, cannot continue")

            curr += int(fat_entry_size)
            cluster += 1

        if None in self.fat:
            raise AssertionError("Unknown error during FAT parsing, please "
                                 "report this error.")