Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyfatfs/FATDirectoryEntry.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

328 statements  

1# -*- coding: utf-8 -*- 

2 

3"""Directory entry operations with PyFAT.""" 

4import posixpath 

5import struct 

6import warnings 

7 

8from time import timezone 

9 

10from pyfatfs.DosDateTime import DosDateTime 

11from pyfatfs.EightDotThree import EightDotThree 

12from pyfatfs._exceptions import PyFATException, NotAnLFNEntryException, \ 

13 BrokenLFNEntryException 

14from pyfatfs import FAT_OEM_ENCODING, FAT_LFN_ENCODING 

15 

16import errno 

17 

18 

19class FATDirectoryEntry: 

20 """Represents directory entries in FAT (files & directories).""" 

21 

22 #: Marks a directory entry as empty 

23 FREE_DIR_ENTRY_MARK = 0xE5 

24 #: Marks all directory entries after this one as empty 

25 LAST_DIR_ENTRY_MARK = 0x00 

26 

27 #: Bit set in DIR_Attr if entry is read-only 

28 ATTR_READ_ONLY = 0x01 

29 #: Bit set in DIR_Attr if entry is hidden 

30 ATTR_HIDDEN = 0x02 

31 #: Bit set in DIR_Attr if entry is a system file 

32 ATTR_SYSTEM = 0x04 

33 #: Bit set in DIR_Attr if entry is a volume id descriptor 

34 ATTR_VOLUME_ID = 0x8 

35 #: Bit set in DIR_Attr if entry is a directory 

36 ATTR_DIRECTORY = 0x10 

37 #: Bit set in DIR_Attr if entry is an archive 

38 ATTR_ARCHIVE = 0x20 

39 #: Bits set in DIR_Attr if entry is an LFN entry 

40 ATTR_LONG_NAME = ATTR_READ_ONLY | ATTR_HIDDEN | \ 

41 ATTR_SYSTEM | ATTR_VOLUME_ID 

42 #: Bitmask to check if entry is an LFN entry 

43 ATTR_LONG_NAME_MASK = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | \ 

44 ATTR_VOLUME_ID | ATTR_DIRECTORY | ATTR_ARCHIVE 

45 

46 #: Directory entry header layout in struct formatted string 

47 FAT_DIRECTORY_LAYOUT = "<11sBBBHHHHHHHL" 

48 #: Size of a directory entry header in bytes 

49 FAT_DIRECTORY_HEADER_SIZE = struct.calcsize(FAT_DIRECTORY_LAYOUT) 

50 #: Maximum allowed file size, dictated by size of DIR_FileSize 

51 MAX_FILE_SIZE = 0xFFFFFFFF 

52 #: Directory entry headers 

53 FAT_DIRECTORY_VARS = ["DIR_Name", "DIR_Attr", "DIR_NTRes", 

54 "DIR_CrtTimeTenth", "DIR_CrtTime", 

55 "DIR_CrtDate", "DIR_LstAccessDate", 

56 "DIR_FstClusHI", "DIR_WrtTime", 

57 "DIR_WrtDate", "DIR_FstClusLO", 

58 "DIR_FileSize"] 

59 

60 def __init__(self, 

61 DIR_Name: EightDotThree, DIR_Attr: int, 

62 DIR_NTRes: int, DIR_CrtTimeTenth: int, 

63 DIR_CrtTime: int, DIR_CrtDate: int, DIR_LstAccessDate: int, 

64 DIR_FstClusHI: int, DIR_WrtTime: int, DIR_WrtDate: int, 

65 DIR_FstClusLO: int, DIR_FileSize: int, 

66 encoding: str = FAT_OEM_ENCODING, 

67 fs: "pyfatfs.PyFat.PyFat" = None, # noqa: F821 

68 lazy_load: bool = False, lfn_entry=None): 

69 """FAT directory entry constructor. 

70 

71 :param DIR_Name: `EightDotThree` class instance 

72 :param DIR_Attr: Attributes of directory 

73 :param DIR_NTRes: Reserved attributes of directory entry 

74 :param DIR_CrtTimeTenth: Milliseconds at file creation 

75 :param DIR_CrtTime: Creation timestamp of entry 

76 :param DIR_CrtDate: Creation date of entry 

77 :param DIR_LstAccessDate: Last access date of entry 

78 :param DIR_FstClusHI: High cluster value of entry data 

79 :param DIR_WrtTime: Modification timestamp of entry 

80 :param DIR_WrtDate: Modification date of entry 

81 :param DIR_FstClusLO: Low cluster value of entry data 

82 :param DIR_FileSize: File size in bytes 

83 :param encoding: Encoding of filename 

84 :param lfn_entry: FATLongDirectoryEntry instance or None 

85 """ 

86 self.__filesize = 0 

87 

88 self.name: EightDotThree = DIR_Name 

89 self.attr = int(DIR_Attr) 

90 self.ntres = int(DIR_NTRes) 

91 self.crttimetenth = int(DIR_CrtTimeTenth) 

92 self.crttime = int(DIR_CrtTime) 

93 self.crtdate = int(DIR_CrtDate) 

94 self.lstaccessdate = int(DIR_LstAccessDate) 

95 self.fstclushi = int(DIR_FstClusHI) 

96 self.wrttime = int(DIR_WrtTime) 

97 self.wrtdate = int(DIR_WrtDate) 

98 self.fstcluslo = int(DIR_FstClusLO) 

99 self.filesize = int(DIR_FileSize) 

100 

101 self.__lazy_load = lazy_load 

102 self.__fs = fs 

103 

104 self._parent = None 

105 

106 # Handle LFN entries 

107 self.lfn_entry = None 

108 try: 

109 self.set_lfn_entry(lfn_entry) 

110 except BrokenLFNEntryException: 

111 warnings.warn("Broken LFN entry detected, omitting " 

112 "long file name.") 

113 

114 self.__dirs = [] 

115 self.__encoding = encoding 

116 

117 @property 

118 def _encoding(self): 

119 return self.__encoding 

120 

121 @property 

122 def filesize(self): 

123 """Size of the file in bytes. 

124 

125 :getter: Get the currently set filesize in bytes 

126 :setter: Set new filesize. FAT chain must be extended 

127 separately. Raises `PyFATException` with 

128 `errno=E2BIG` if filesize is larger than 

129 `FATDirectoryEntry.MAX_FILE_SIZE`. 

130 :type: int 

131 """ 

132 return self.__filesize 

133 

134 @filesize.setter 

135 def filesize(self, size: int): 

136 if size > self.MAX_FILE_SIZE: 

137 raise PyFATException(f"Specified file size {size} too large " 

138 f"for FAT-based filesystems.", 

139 errno=errno.E2BIG) 

140 

141 self.__filesize = size 

142 

143 @staticmethod 

144 def new(name: EightDotThree, tz: timezone, encoding: str, 

145 attr: int = 0, ntres: int = 0, cluster: int = 0, 

146 filesize: int = 0) -> "FATDirectoryEntry": 

147 """Create a new directory entry with sane defaults. 

148 

149 :param name: ``EightDotThree``: SFN of new dentry 

150 :param tz: ``timezone``: Timezone value to use for new timestamp 

151 :param encoding: ``str``: Encoding for SFN 

152 :param attr: ``int``: Directory attributes 

153 :param ntres: ``int``: Reserved NT directory attributes 

154 :param cluster: ``int``: Cluster number of dentry 

155 :param filesize: ``int``: Size of file referenced by dentry 

156 :returns: ``FATDirectoryEntry`` instance 

157 """ 

158 dt = DosDateTime.now(tz=tz) 

159 dentry = FATDirectoryEntry( 

160 DIR_Name=name, 

161 DIR_Attr=attr, 

162 DIR_NTRes=ntres, 

163 DIR_CrtTimeTenth=0, 

164 DIR_CrtTime=dt.serialize_time(), 

165 DIR_CrtDate=dt.serialize_date(), 

166 DIR_LstAccessDate=dt.serialize_date(), 

167 DIR_FstClusHI=0x00, 

168 DIR_WrtTime=dt.serialize_time(), 

169 DIR_WrtDate=dt.serialize_date(), 

170 DIR_FstClusLO=0x00, 

171 DIR_FileSize=filesize, 

172 encoding=encoding 

173 ) 

174 dentry.set_cluster(cluster) 

175 return dentry 

176 

177 def get_ctime(self) -> DosDateTime: 

178 """Get dentry creation time.""" 

179 return self.__combine_dosdatetime(self.crtdate, self.crttime) 

180 

181 def get_mtime(self) -> DosDateTime: 

182 """Get dentry modification time.""" 

183 return self.__combine_dosdatetime(self.wrtdate, self.wrttime) 

184 

185 def get_atime(self) -> DosDateTime: 

186 """Get dentry access time.""" 

187 return DosDateTime.deserialize_date(self.lstaccessdate) 

188 

189 @staticmethod 

190 def __combine_dosdatetime(dt, tm) -> DosDateTime: 

191 dt = DosDateTime.deserialize_date(dt) 

192 return dt.combine(dt, DosDateTime.deserialize_time(tm)) 

193 

194 def get_checksum(self) -> int: 

195 """Get calculated checksum of this directory entry. 

196 

197 :returns: Checksum as int 

198 """ 

199 return self.name.checksum() 

200 

201 def set_lfn_entry(self, lfn_entry): 

202 """Set LFN entry for current directory entry. 

203 

204 :param: lfn_entry: Can be either of type `FATLongDirectoryEntry` 

205 or `None`. 

206 """ 

207 if not isinstance(lfn_entry, FATLongDirectoryEntry): 

208 return 

209 

210 # Verify LFN entries checksums 

211 chksum = self.get_checksum() 

212 for entry in lfn_entry.lfn_entries: 

213 entry_chksum = lfn_entry.lfn_entries[entry]["LDIR_Chksum"] 

214 if entry_chksum != chksum: 

215 raise BrokenLFNEntryException(f'Checksum verification for ' 

216 f'LFN entry of directory ' 

217 f'"{self.get_short_name()}" ' 

218 f'failed') 

219 self.lfn_entry = lfn_entry 

220 

221 def get_entry_size(self): 

222 """Get size of directory entry. 

223 

224 :returns: Entry size in bytes as int 

225 """ 

226 if self.is_directory(): 

227 self.__populate_dirs() 

228 

229 sz = self.FAT_DIRECTORY_HEADER_SIZE 

230 if isinstance(self.lfn_entry, FATLongDirectoryEntry): 

231 sz *= len(self.lfn_entry.lfn_entries) 

232 sz += self.FAT_DIRECTORY_HEADER_SIZE * len(self.__dirs)+1 

233 

234 return sz 

235 

236 def get_size(self): 

237 """Get filesize or directory entry size. 

238 

239 :returns: Filesize or directory entry size in bytes as int 

240 """ 

241 import warnings 

242 warnings.warn(f"{self.__class__}.get_size is deprecated, this " 

243 f"method will be removed in PyFatFS 2.0; please " 

244 f"use the filesize property instead!", 

245 DeprecationWarning) 

246 return self.filesize 

247 

248 def set_size(self, size: int): 

249 """Set filesize. 

250 

251 :param size: `int`: File size in bytes 

252 """ 

253 import warnings 

254 warnings.warn(f"{self.__class__}.set_size is deprecated, this " 

255 f"method will be removed in PyFatFS 2.0; please " 

256 f"use the filesize property instead!", 

257 DeprecationWarning) 

258 self.filesize = size 

259 

260 def get_cluster(self): 

261 """Get cluster address of directory entry. 

262 

263 :returns: Cluster address of entry 

264 """ 

265 return self.fstcluslo + (self.fstclushi << 16) 

266 

267 def set_cluster(self, first_cluster): 

268 """Set low and high cluster address in directory headers.""" 

269 self.fstcluslo = (first_cluster >> (16 * 0) & 0xFFFF) 

270 self.fstclushi = (first_cluster >> (16 * 1) & 0xFFFF) 

271 

272 def __bytes__(self): 

273 """Represent directory entry as bytes. 

274 

275 Note: Also represents accompanying LFN entries 

276 

277 :returns: Entry & LFN entry as bytes-object 

278 """ 

279 entry = b'' 

280 if isinstance(self.lfn_entry, FATLongDirectoryEntry): 

281 entry += bytes(self.lfn_entry) 

282 

283 entry += struct.pack(self.FAT_DIRECTORY_LAYOUT, 

284 self.name.name, 

285 self.attr, self.ntres, self.crttimetenth, 

286 self.crttime, self.crtdate, self.lstaccessdate, 

287 self.fstclushi, self.wrttime, self.wrtdate, 

288 self.fstcluslo, self.filesize) 

289 

290 return entry 

291 

292 def _add_parent(self, cls): 

293 """Add parent directory link to current directory entry. 

294 

295 raises: PyFATException 

296 """ 

297 if self._parent is not None: 

298 raise PyFATException("Trying to add multiple parents to current " 

299 "directory!", errno=errno.ETOOMANYREFS) 

300 

301 if not isinstance(cls, FATDirectoryEntry): 

302 raise PyFATException("Trying to add a non-FAT directory entry " 

303 "as parent directory!", errno=errno.EBADE) 

304 

305 self._parent = cls 

306 

307 def _get_parent_dir(self, sd): 

308 """Build path name for recursive directory entries.""" 

309 name = self.__repr__() 

310 if self.__repr__() == "/": 

311 name = "" 

312 sd += [name] 

313 

314 if self._parent is None: 

315 return sd 

316 

317 return self._parent._get_parent_dir(sd) 

318 

319 def get_full_path(self): 

320 """Iterate all parents up and join them by "/".""" 

321 parent_dirs = [self.__repr__()] 

322 

323 if self._parent is None: 

324 return "/" 

325 

326 return posixpath.join(*list(reversed( 

327 self._parent._get_parent_dir(parent_dirs)))) 

328 

329 def get_parent_dir(self): 

330 """Get the parent directory entry.""" 

331 if self._parent is None: 

332 raise PyFATException("Cannot query parent directory of " 

333 "root directory", errno=errno.ENOENT) 

334 

335 return self._parent 

336 

337 def is_special(self): 

338 """Determine if dir entry is a dot or dotdot entry. 

339 

340 :returns: Boolean value whether or not entry is 

341 a dot or dotdot entry 

342 """ 

343 return self.get_short_name() in [".", ".."] 

344 

345 def is_read_only(self): 

346 """Determine if dir entry has read-only attribute set. 

347 

348 :returns: Boolean value indicating read-only attribute is set 

349 """ 

350 return (self.ATTR_READ_ONLY & self.attr) > 0 

351 

352 def is_hidden(self): 

353 """Determine if dir entry has the hidden attribute set. 

354 

355 :returns: Boolean value indicating hidden attribute is set 

356 """ 

357 return (self.ATTR_HIDDEN & self.attr) > 0 

358 

359 def is_system(self): 

360 """Determine if dir entry has the system file attribute set. 

361 

362 :returns: Boolean value indicating system attribute is set 

363 """ 

364 return (self.ATTR_SYSTEM & self.attr) > 0 

365 

366 def is_volume_id(self): 

367 """Determine if dir entry has the volume ID attribute set. 

368 

369 :returns: Boolean value indicating volume ID attribute is set 

370 """ 

371 return (self.ATTR_VOLUME_ID & self.attr) > 0 

372 

373 def _verify_is_directory(self): 

374 """Verify that current entry is a directory. 

375 

376 raises: PyFATException: If current entry is not a directory. 

377 """ 

378 if not self.is_directory(): 

379 raise PyFATException("Cannot get entries of this entry, as " 

380 "it is not a directory.", 

381 errno=errno.ENOTDIR) 

382 

383 def is_directory(self): 

384 """Determine if dir entry has directory attribute set. 

385 

386 :returns: Boolean value indicating directory attribute is set 

387 """ 

388 return (self.ATTR_DIRECTORY & self.attr) > 0 

389 

390 def is_archive(self): 

391 """Determine if dir entry has archive attribute set. 

392 

393 :returns: Boolean value indicating archive attribute is set 

394 """ 

395 return (self.ATTR_ARCHIVE & self.attr) > 0 

396 

397 def is_empty(self): 

398 """Determine if directory does not contain any directories.""" 

399 self._verify_is_directory() 

400 self.__populate_dirs() 

401 

402 for d in self.__dirs: 

403 if d.is_special(): 

404 continue 

405 return False 

406 

407 return True 

408 

409 def __populate_dirs(self): 

410 if self.__lazy_load is False: 

411 return 

412 

413 clus = self.get_cluster() 

414 self.__dirs = self.__fs.parse_dir_entries_in_cluster_chain(clus) 

415 for dir_entry in self.__dirs: 

416 dir_entry._add_parent(self) 

417 self.__lazy_load = False 

418 

419 def _get_entries_raw(self): 

420 """Get a full list of entries in current directory.""" 

421 self._verify_is_directory() 

422 self.__populate_dirs() 

423 

424 return self.__dirs 

425 

426 def get_entries(self): 

427 """Get entries of directory. 

428 

429 :raises: PyFatException: If entry is not a directory 

430 :returns: tuple: root (current path, full), 

431 dirs (all dirs), files (all files) 

432 """ 

433 dirs = [] 

434 files = [] 

435 specials = [] 

436 

437 for d in self._get_entries_raw(): 

438 if d.is_special() or d.is_volume_id(): 

439 # Volume IDs and dot/dotdot entries 

440 specials += [d] 

441 elif d.is_directory(): 

442 # Directories 

443 dirs += [d] 

444 else: 

445 # Everything else must be a file 

446 files += [d] 

447 

448 return dirs, files, specials 

449 

450 def _search_entry(self, name: str): 

451 """Find given dir entry by walking current dir. 

452 

453 :param name: Name of entry to search for 

454 :raises: PyFATException: If entry cannot be found 

455 :returns: FATDirectoryEntry: Found entry 

456 """ 

457 dirs, files, _ = self.get_entries() 

458 for entry in dirs+files: 

459 try: 

460 if entry.get_long_name() == name: 

461 return entry 

462 except NotAnLFNEntryException: 

463 pass 

464 if entry.get_short_name() == name: 

465 return entry 

466 

467 raise PyFATException(f'Cannot find entry {name}', 

468 errno=errno.ENOENT) 

469 

470 def get_entry(self, path: str): 

471 """Get sub-entry if current entry is a directory. 

472 

473 :param path: Relative path of entry to get 

474 :raises: PyFATException: If entry cannot be found 

475 :returns: FATDirectoryEntry: Found entry 

476 """ 

477 entry = self 

478 for segment in filter(None, path.split("/")): 

479 entry._verify_is_directory() 

480 entry = entry._search_entry(segment) 

481 return entry 

482 

483 def walk(self): 

484 """Walk all directory entries recursively. 

485 

486 :returns: tuple: root (current path, full), 

487 dirs (all dirs), files (all files) 

488 """ 

489 self._verify_is_directory() 

490 self.__populate_dirs() 

491 

492 root = self.get_full_path() 

493 dirs, files, _ = self.get_entries() 

494 

495 yield root, dirs, files 

496 for d in self.__dirs: 

497 if d.is_special(): 

498 # Ignore dot and dotdot 

499 continue 

500 

501 if not d.is_directory(): 

502 continue 

503 

504 yield from d.walk() 

505 

506 def add_subdirectory(self, dir_entry, recursive: bool = True): 

507 """Register a subdirectory in current directory entry. 

508 

509 :param dir_entry: FATDirectoryEntry 

510 :raises: PyFATException: If current entry is not a directory or 

511 given directory entry already has a parent 

512 directory set 

513 """ 

514 # Check if current dir entry is even a directory! 

515 self._verify_is_directory() 

516 self.__populate_dirs() 

517 

518 dir_entry._add_parent(self) 

519 self.__dirs += [dir_entry] 

520 

521 def mark_empty(self): 

522 """Mark this directory entry as empty.""" 

523 # Also mark LFN entries as empty 

524 try: 

525 self.lfn_entry.mark_empty() 

526 except AttributeError: 

527 pass 

528 

529 self.name.name[0] = self.FREE_DIR_ENTRY_MARK 

530 

531 def remove_dir_entry(self, name): 

532 """Remove given dir_entry from dir list. 

533 

534 **NOTE:** This will also remove special entries such 

535 as ».«, »..« and volume labels! 

536 """ 

537 # Iterate all entries 

538 for dir_entry in self._get_entries_raw(): 

539 sn = dir_entry.get_short_name() 

540 try: 

541 ln = dir_entry.get_long_name() 

542 except NotAnLFNEntryException: 

543 ln = None 

544 if name in [sn, ln]: 

545 self.__dirs.remove(dir_entry) 

546 return 

547 

548 raise PyFATException(f"Cannot remove '{name}', no such " 

549 f"file or directory!", errno=errno.ENOENT) 

550 

551 def __repr__(self): 

552 """String-represent directory entry by (preferably) LFN. 

553 

554 :returns: str: Long file name if existing, 8DOT3 otherwise 

555 """ 

556 try: 

557 return self.get_long_name() 

558 except NotAnLFNEntryException: 

559 return self.get_short_name() 

560 

561 def get_short_name(self): 

562 """Get short name of directory entry. 

563 

564 :returns: str: Name of directory entry 

565 """ 

566 return self.name.get_unpadded_filename() 

567 

568 def get_long_name(self): 

569 """Get long name of directory entry. 

570 

571 :raises: NotAnLFNEntryException: If entry has no long file name 

572 :returns: str: Long file name of directory entry 

573 """ 

574 if self.lfn_entry is None: 

575 raise NotAnLFNEntryException("No LFN entry found for this " 

576 "dir entry.") 

577 

578 return str(self.lfn_entry) 

579 

580 

581class FATLongDirectoryEntry(object): 

582 """Represents long file name (LFN) entries.""" 

583 

584 #: LFN entry header layout in struct formatted string 

585 FAT_LONG_DIRECTORY_LAYOUT = "<B10sBBB12sH4s" 

586 #: LFN header fields when extracted with `FAT_LONG_DIRECTORY_LAYOUT` 

587 FAT_LONG_DIRECTORY_VARS = ["LDIR_Ord", "LDIR_Name1", "LDIR_Attr", 

588 "LDIR_Type", "LDIR_Chksum", "LDIR_Name2", 

589 "LDIR_FstClusLO", "LDIR_Name3"] 

590 #: Ordinance of last LFN entry in a chain 

591 LAST_LONG_ENTRY = 0x40 

592 #: Length for long file name in bytes per entry 

593 LFN_ENTRY_LENGTH = 26 

594 

595 def __init__(self): 

596 """Initialize empty LFN directory entry object.""" 

597 self.lfn_entries = {} 

598 

599 def get_entries(self, reverse: bool = False): 

600 """Get LFS entries in correct order (based on `LDIR_Ord`). 

601 

602 :param reverse: `bool`: Returns LFN entries in reversed order. 

603 This is required for byte representation. 

604 """ 

605 for _, e in sorted(self.lfn_entries.items(), 

606 key=lambda x: x[1]["LDIR_Ord"], 

607 reverse=reverse): 

608 yield e 

609 

610 def mark_empty(self): 

611 """Mark LFN entry as empty.""" 

612 free_dir_entry_mark = FATDirectoryEntry.FREE_DIR_ENTRY_MARK 

613 for k in self.lfn_entries.keys(): 

614 self.lfn_entries[k]["LDIR_Ord"] = free_dir_entry_mark 

615 

616 def __bytes__(self): 

617 """Represent LFN entries as bytes.""" 

618 entries_bytes = b"" 

619 for e in self.get_entries(reverse=True): 

620 entries_bytes += struct.pack(self.FAT_LONG_DIRECTORY_LAYOUT, 

621 e["LDIR_Ord"], e["LDIR_Name1"], 

622 e["LDIR_Attr"], e["LDIR_Type"], 

623 e["LDIR_Chksum"], e["LDIR_Name2"], 

624 e["LDIR_FstClusLO"], e["LDIR_Name3"]) 

625 return entries_bytes 

626 

627 def __str__(self): 

628 """Remove padding from LFN entry and decode it. 

629 

630 :returns: `str` decoded string of filename 

631 """ 

632 name = b'' 

633 

634 for e in self.get_entries(): 

635 for h in ["LDIR_Name1", "LDIR_Name2", "LDIR_Name3"]: 

636 name += e[h] 

637 

638 while name.endswith(b'\xFF\xFF'): 

639 name = name[:-2] 

640 

641 name = name.decode(FAT_LFN_ENCODING) 

642 

643 if name.endswith('\0'): 

644 name = name[:-1] 

645 

646 return name 

647 

648 @staticmethod 

649 def is_lfn_entry(LDIR_Ord, LDIR_Attr): 

650 """Verify that entry is an LFN entry. 

651 

652 :param LDIR_Ord: First byte of the directory header, ordinance 

653 :param LDIR_Attr: Attributes segment of directory header 

654 :returns: `True` if entry is a valid LFN entry 

655 """ 

656 lfn_attr = FATDirectoryEntry.ATTR_LONG_NAME 

657 lfn_attr_mask = FATDirectoryEntry.ATTR_LONG_NAME_MASK 

658 is_attr_set = (LDIR_Attr & lfn_attr_mask) == lfn_attr 

659 

660 return is_attr_set and \ 

661 LDIR_Ord != FATDirectoryEntry.FREE_DIR_ENTRY_MARK 

662 

663 def add_lfn_entry(self, LDIR_Ord, LDIR_Name1, LDIR_Attr, LDIR_Type, 

664 LDIR_Chksum, LDIR_Name2, LDIR_FstClusLO, LDIR_Name3): 

665 """Add LFN entry to this instances chain. 

666 

667 :param LDIR_Ord: Ordinance of LFN entry 

668 :param LDIR_Name1: First name field of LFN entry 

669 :param LDIR_Attr: Attributes of LFN entry 

670 :param LDIR_Type: Type of LFN entry 

671 :param LDIR_Chksum: Checksum value of following 8dot3 entry 

672 :param LDIR_Name2: Second name field of LFN entry 

673 :param LDIR_FstClusLO: Cluster address of LFN entry. Always zero. 

674 :param LDIR_Name3: Third name field of LFN entry 

675 """ 

676 # Check if attribute matches 

677 if not self.is_lfn_entry(LDIR_Ord, LDIR_Attr): 

678 raise NotAnLFNEntryException("Given LFN entry is not a long " 

679 "file name entry or attribute " 

680 "not set correctly!") 

681 

682 # Check if FstClusLO is 0, as required by the spec 

683 if LDIR_FstClusLO != 0: 

684 raise PyFATException("Given LFN entry has an invalid first " 

685 "cluster ID, don't know what to do.", 

686 errno=errno.EFAULT) 

687 

688 # Check if item with same index has already been added 

689 if LDIR_Ord in self.lfn_entries.keys(): 

690 raise PyFATException("Given LFN entry part with index \'{}\'" 

691 "has already been added to LFN " 

692 "entry list.".format(LDIR_Ord)) 

693 

694 mapped_entries = dict(zip(self.FAT_LONG_DIRECTORY_VARS, 

695 (LDIR_Ord, LDIR_Name1, LDIR_Attr, LDIR_Type, 

696 LDIR_Chksum, LDIR_Name2, LDIR_FstClusLO, 

697 LDIR_Name3))) 

698 

699 self.lfn_entries[LDIR_Ord] = mapped_entries 

700 

701 def is_lfn_entry_complete(self): 

702 """Verify that LFN object forms a complete chain. 

703 

704 :returns: `True` if `LAST_LONG_ENTRY` is found 

705 """ 

706 for k in self.lfn_entries.keys(): 

707 if (int(k) & self.LAST_LONG_ENTRY) == self.LAST_LONG_ENTRY: 

708 return True 

709 

710 return False 

711 

712 

713def make_lfn_entry(dir_name: str, 

714 short_name: EightDotThree): 

715 """Generate a `FATLongDirectoryEntry` instance from directory name. 

716 

717 :param dir_name: Long name of directory 

718 :param short_name: `EightDotThree` class instance 

719 :raises: `PyFATException` if entry name does not require an LFN 

720 entry or the name exceeds the FAT limitation of 255 characters 

721 """ 

722 lfn_entry = FATLongDirectoryEntry() 

723 #: Length in bytes of an LFN entry 

724 lfn_entry_length = 26 

725 dir_name_str = dir_name 

726 dir_name = dir_name.encode(FAT_LFN_ENCODING) 

727 dir_name_modulus = len(dir_name) % lfn_entry_length 

728 

729 if EightDotThree.is_8dot3_conform(dir_name_str, 

730 encoding=short_name.encoding): 

731 raise PyFATException("Directory entry is already 8.3 conform, " 

732 "no need to create an LFN entry.", 

733 errno=errno.EINVAL) 

734 

735 if len(dir_name) > 255: 

736 raise PyFATException("Long file name exceeds 255 " 

737 "characters, not supported.", 

738 errno=errno.ENAMETOOLONG) 

739 

740 checksum = short_name.checksum() 

741 

742 if dir_name_modulus != 0: 

743 # Null-terminate string if required 

744 dir_name += '\0'.encode(FAT_LFN_ENCODING) 

745 

746 # Fill the rest with 0xFF if it doesn't fit evenly 

747 new_sz = lfn_entry_length - len(dir_name) 

748 new_sz %= lfn_entry_length 

749 new_sz += len(dir_name) 

750 dir_name += b'\xFF' * (new_sz - len(dir_name)) 

751 

752 # Generate linked LFN entries 

753 lfn_entries = len(dir_name) // lfn_entry_length 

754 for i in range(lfn_entries): 

755 if i == lfn_entries-1: 

756 lfn_entry_ord = 0x40 | i+1 

757 else: 

758 lfn_entry_ord = i+1 

759 

760 n = i*lfn_entry_length 

761 dirname1 = dir_name[n:n+10] 

762 n += 10 

763 dirname2 = dir_name[n:n+12] 

764 n += 12 

765 dirname3 = dir_name[n:n+4] 

766 

767 lfn_entry.add_lfn_entry(LDIR_Ord=lfn_entry_ord, 

768 LDIR_Name1=dirname1, 

769 LDIR_Attr=FATDirectoryEntry.ATTR_LONG_NAME, 

770 LDIR_Type=0x00, 

771 LDIR_Chksum=checksum, 

772 LDIR_Name2=dirname2, 

773 LDIR_FstClusLO=0, 

774 LDIR_Name3=dirname3) 

775 return lfn_entry