Exemple #1
0
def test_make_8dot3_name_unicode():
    """Test that make_8dot3_filename generates valid 8dot3 filenames."""
    fde = mock.MagicMock()
    fde.get_entries.return_value = ([], [], [])
    sfn = EightDotThree()
    lfn = sfn.make_8dot3_name("­Ъци.­Ъци", fde)
    assert "_._" == lfn
    assert sfn.is_8dot3_conform(lfn)
Exemple #2
0
def test_make_8dot3_name_cut_ext():
    """Test that make_8dot3_filename generates valid 8dot3 filenames."""
    fde = mock.MagicMock()
    fde.get_entries.return_value = ([], [], [])
    sfn = EightDotThree()
    lfn = sfn.make_8dot3_name("This is a long filename.TeXT", fde)
    assert "THISIS.TEX" == lfn
    assert sfn.is_8dot3_conform(lfn)
Exemple #3
0
def test_checksum_calculation_referenceimpl_random():
    """Test that the checksum calculation works, following reference impl."""
    base = ''.join(random.choices(string.ascii_letters + string.digits,
                                  k=8)).upper()
    ext = ''.join(random.choices(string.ascii_letters + string.digits,
                                 k=3)).upper()
    sfn = EightDotThree()
    sfn.set_str_name(f"{base}.{ext}")

    assert sfn.checksum() == calculate_checksum_referenceimpl(sfn.name)
Exemple #4
0
def test_make_8dot3_name_collision():
    """Test that make_8dot3_filename generates valid 8dot3 filenames."""
    fde = mock.MagicMock()
    fde_sub = mock.MagicMock()
    fde_sub.get_short_name.side_effect = [
        "THISIS.TXT", "THISIS~1.TXT", "THISIS~2.TXT"
    ]
    fde.get_entries.return_value = ([fde_sub, fde_sub], [fde_sub], [])
    sfn = EightDotThree()
    lfn = sfn.make_8dot3_name("This is a long filename.txt", fde)
    assert "THISIS~3.TXT" == lfn
    assert sfn.is_8dot3_conform(lfn)
Exemple #5
0
def test_make_8dot3_name():
    """Test that make_8dot3_filename generates valid 8dot3 filenames."""
    fde = mock.MagicMock()
    fde.get_entries.return_value = ([], [], [])
    sfn = EightDotThree()
    n = sfn.make_8dot3_name("This is a long filename.txt", fde)
    sfn.set_str_name(n)
    assert "THISIS.TXT" == n
    assert sfn.is_8dot3_conform(sfn.get_unpadded_filename())
Exemple #6
0
    def parse_root_dir(self):
        """Parse root directory entry."""
        root_dir_sfn = EightDotThree()
        root_dir_sfn.set_str_name("")
        dir_attr = FATDirectoryEntry.ATTR_DIRECTORY
        self.root_dir = FATDirectoryEntry(DIR_Name=root_dir_sfn,
                                          DIR_Attr=dir_attr,
                                          DIR_NTRes=0,
                                          DIR_CrtTimeTenth=0,
                                          DIR_CrtTime=0,
                                          DIR_CrtDate=0,
                                          DIR_LstAccessDate=0,
                                          DIR_FstClusHI=0,
                                          DIR_WrtTime=0,
                                          DIR_WrtDate=0,
                                          DIR_FstClusLO=0,
                                          DIR_FileSize=0,
                                          encoding=self.encoding)

        if self.fat_type in [self.FAT_TYPE_FAT12, self.FAT_TYPE_FAT16]:
            self._fat12_parse_root_dir()
        else:
            self._fat32_parse_root_dir()
Exemple #7
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
Exemple #8
0
    def makedir(self, path: str, permissions: Permissions = None,
                recreate: bool = False):
        """Create directory on filesystem.

        :param path: Path of new directory on filesystem
        :param permissions: Currently not implemented
        :param recreate: Ignore if directory already exists
        """
        path = normpath(path)
        base = split(path)[0]
        dirname = split(path)[1]

        # Plausability checks
        try:
            self.opendir(base)
        except DirectoryExpected:
            raise ResourceNotFound(path)
        base = self._get_dir_entry(base)

        try:
            self._get_dir_entry(path)
        except ResourceNotFound:
            pass
        else:
            if not recreate:
                raise DirectoryExists(path)
            else:
                # TODO: Update mtime
                return SubFS(self, path)

        parent_is_root = base == self.fs.root_dir

        # Determine 8DOT3 file name + LFN
        short_name = EightDotThree()
        n = short_name.make_8dot3_name(dirname, base)
        short_name.set_str_name(n)

        dt = DosDateTime.now(tz=self.tz)

        newdir = FATDirectoryEntry(DIR_Name=short_name,
                                   DIR_Attr=FATDirectoryEntry.ATTR_DIRECTORY,
                                   DIR_NTRes=0,
                                   DIR_CrtTimeTenth=0,
                                   DIR_CrtTime=dt.serialize_time(),
                                   DIR_CrtDate=dt.serialize_date(),
                                   DIR_LstAccessDate=dt.serialize_date(),
                                   DIR_FstClusHI=0x00,
                                   DIR_WrtTime=dt.serialize_time(),
                                   DIR_WrtDate=dt.serialize_date(),
                                   DIR_FstClusLO=0x00,
                                   DIR_FileSize=0,
                                   encoding=self.fs.encoding)

        # Create LFN entry if required
        _sfn = short_name.get_unpadded_filename()
        if _sfn != dirname.upper() or (_sfn != dirname and self.preserve_case):
            lfn_entry = make_lfn_entry(dirname, short_name)
            newdir.set_lfn_entry(lfn_entry)

        # Create . and .. directory entries
        first_cluster = self.fs.allocate_bytes(
            FATDirectoryEntry.FAT_DIRECTORY_HEADER_SIZE * 2,
            erase=True)[0]
        newdir.set_cluster(first_cluster)
        dot_sn = EightDotThree()
        dot_sn.set_byte_name(b".          ")
        dot = FATDirectoryEntry(DIR_Name=dot_sn,
                                DIR_Attr=FATDirectoryEntry.ATTR_DIRECTORY,
                                DIR_NTRes=newdir.ntres,
                                DIR_CrtTimeTenth=newdir.crttimetenth,
                                DIR_CrtTime=newdir.crttime,
                                DIR_CrtDate=newdir.crtdate,
                                DIR_LstAccessDate=newdir.lstaccessdate,
                                DIR_FstClusHI=newdir.fstclushi,
                                DIR_WrtTime=newdir.wrttime,
                                DIR_WrtDate=newdir.wrtdate,
                                DIR_FstClusLO=newdir.fstcluslo,
                                DIR_FileSize=newdir.filesize,
                                encoding=self.fs.encoding)
        dotdot_sn = EightDotThree()
        dotdot_sn.set_byte_name(b"..         ")
        base_fstclushi = base.fstclushi if not parent_is_root else 0x0
        base_fstcluslo = base.fstcluslo if not parent_is_root else 0x0
        dotdot = FATDirectoryEntry(DIR_Name=dotdot_sn,
                                   DIR_Attr=FATDirectoryEntry.ATTR_DIRECTORY,
                                   DIR_NTRes=base.ntres,
                                   DIR_CrtTimeTenth=base.crttimetenth,
                                   DIR_CrtTime=base.crttime,
                                   DIR_CrtDate=base.crtdate,
                                   DIR_LstAccessDate=base.lstaccessdate,
                                   DIR_FstClusHI=base_fstclushi,
                                   DIR_WrtTime=base.wrttime,
                                   DIR_WrtDate=base.wrtdate,
                                   DIR_FstClusLO=base_fstcluslo,
                                   DIR_FileSize=base.filesize,
                                   encoding=self.fs.encoding)
        newdir.add_subdirectory(dot)
        newdir.add_subdirectory(dotdot)

        # Write new directory contents
        self.fs.update_directory_entry(newdir)

        # Write parent directory
        base.add_subdirectory(newdir)
        self.fs.update_directory_entry(base)

        # Flush FAT(s) to disk
        self.fs.flush_fat()

        return SubFS(self, path)
Exemple #9
0
    def create(self, path: str, wipe: bool = False) -> bool:
        """Create a new file.

        :param path: Path of new file on filesystem
        :param wipe: Overwrite existing file contents
        """
        basename = "/".join(path.split("/")[:-1])
        dirname = path.split("/")[-1]

        # Plausability checks
        try:
            self.opendir(basename)
        except DirectoryExpected:
            raise ResourceNotFound(path)
        base = self._get_dir_entry(basename)

        try:
            dentry = self._get_dir_entry(path)
        except ResourceNotFound:
            pass
        else:
            if not wipe:
                return False
            else:
                # Clean up existing file contents
                # TODO: touch {a,m}time
                dentry.set_size(0)
                old_cluster = dentry.get_cluster()
                dentry.set_cluster(0)
                self.fs.free_cluster_chain(old_cluster)
                return True

        # Determine 8DOT3 file name + LFN
        short_name = EightDotThree()
        n = short_name.make_8dot3_name(dirname, base)
        short_name.set_str_name(n)

        dt = DosDateTime.now(tz=self.tz)

        newdir = FATDirectoryEntry(DIR_Name=short_name,
                                   DIR_Attr=0,
                                   DIR_NTRes=0,
                                   DIR_CrtTimeTenth=0,
                                   DIR_CrtTime=dt.serialize_time(),
                                   DIR_CrtDate=dt.serialize_date(),
                                   DIR_LstAccessDate=dt.serialize_date(),
                                   DIR_FstClusHI=0x00,
                                   DIR_WrtTime=dt.serialize_time(),
                                   DIR_WrtDate=dt.serialize_date(),
                                   DIR_FstClusLO=0x00,
                                   DIR_FileSize=0,
                                   encoding=self.fs.encoding)

        # Create LFN entry if required
        _sfn = short_name.get_unpadded_filename()
        if _sfn != dirname.upper() or (_sfn != dirname and self.preserve_case):
            lfn_entry = make_lfn_entry(dirname, short_name)
            newdir.set_lfn_entry(lfn_entry)

        # Write reference to parent directory
        base.add_subdirectory(newdir)
        self.fs.update_directory_entry(base)

        # Flush FAT(s) to disk
        self.fs.flush_fat()
        return True
Exemple #10
0
def test_checksum_calculation_referenceimpl():
    """Test that the checksum calculation works, following reference impl."""
    sfn = EightDotThree()
    sfn.set_str_name("FILENAME.TXT")
    assert sfn.checksum() == calculate_checksum_referenceimpl(sfn.name)
Exemple #11
0
def test_checksum_calculation_precalculated():
    """Test that the checksum calculation works, with a precalculated value."""
    sfn = EightDotThree()
    sfn.set_str_name("FILENAME.TXT")
    assert sfn.checksum() == 58
Exemple #12
0
def test_bytename_set_not_a_directory_free_entry():
    """Test that 0x00 is detect as the last entry of the directory cluster."""
    sfn = EightDotThree()
    with pytest.raises(NotADirectoryError) as ex:
        sfn.set_byte_name(b'\xE5          ')
        assert ex.free_type == ex.FREE_ENTRY
Exemple #13
0
def test_is_8dot3_conform_false():
    """Test that non-8.3 file names are correctly detected."""
    assert not EightDotThree.is_8dot3_conform("This is a Long file.txt")
Exemple #14
0
def test_is_8dot3_conform_true():
    """Test that 8.3 file names are correctly detected."""
    assert EightDotThree.is_8dot3_conform("TESTFILE.TXT")
Exemple #15
0
def test_bytename_set_invalid_length():
    """Test that a byte name can only be set with the correct length."""
    sfn = EightDotThree()
    for n in [b'', b'1234567890', b'123456789011']:
        with pytest.raises(ValueError):
            sfn.set_byte_name(n)
Exemple #16
0
    def parse_dir_entries_in_address(
            self,
            address: int = 0,
            max_address: int = 0,
            tmp_lfn_entry: FATLongDirectoryEntry = None):
        """Parse directory entries in address range."""
        if tmp_lfn_entry is None:
            tmp_lfn_entry = FATLongDirectoryEntry()

        dir_hdr_size = FATDirectoryEntry.FAT_DIRECTORY_HEADER_SIZE

        if max_address == 0:
            max_address = FATDirectoryEntry.FAT_DIRECTORY_HEADER_SIZE

        dir_entries = []

        for hdr_addr in range(address, max_address, dir_hdr_size):
            # Parse each entry
            dir_hdr = self.__parse_dir_entry(hdr_addr)
            dir_sn = EightDotThree(encoding=self.encoding)
            dir_first_byte = dir_hdr["DIR_Name"][0]
            try:
                dir_sn.set_byte_name(dir_hdr["DIR_Name"])
            except NotAFatEntryException as ex:
                # Not a directory of any kind, invalidate temporary LFN entries
                tmp_lfn_entry = FATLongDirectoryEntry()
                if ex.free_type == ex.FREE_ENTRY:
                    # Empty directory entry,
                    continue
                elif ex.free_type == ex.LAST_ENTRY:
                    # Last directory entry, do not parse any further
                    break
            else:
                dir_hdr["DIR_Name"] = dir_sn

            # Long File Names
            if FATLongDirectoryEntry.is_lfn_entry(dir_first_byte,
                                                  dir_hdr["DIR_Attr"]):
                self.parse_lfn_entry(tmp_lfn_entry, hdr_addr)
                continue

            # Normal directory entries
            if not tmp_lfn_entry.is_lfn_entry_complete():
                # Ignore incomplete LFN entries altogether
                tmp_lfn_entry = None

            dir_entry = FATDirectoryEntry(encoding=self.encoding,
                                          lfn_entry=tmp_lfn_entry,
                                          **dir_hdr)
            dir_entries += [dir_entry]

            if dir_entry.is_directory() and not dir_entry.is_special():
                # Iterate all subdirectories except for dot and dotdot
                cluster = dir_entry.get_cluster()
                subdirs = self.parse_dir_entries_in_cluster_chain(cluster)
                for d in subdirs:
                    dir_entry.add_subdirectory(d)

            # Reset temporary LFN entry
            tmp_lfn_entry = FATLongDirectoryEntry()

        return dir_entries, tmp_lfn_entry