From de1a61159c88f58754229e343f8b5e74ef303a66 Mon Sep 17 00:00:00 2001 From: maxpat78 Date: Tue, 17 Oct 2023 12:45:57 +0200 Subject: [PATCH] Added wiping free space capability - new Dirtable.wipefreespace member to zero unallocated clusters - new wipe tool - ability to optimize virtual disk containers combining wipe with imgclone tool - fixed a small help-bug in mkvdisk.py --- FATtools/FAT.py | 13 +++++++++++- FATtools/exFAT.py | 12 +++++++++++ FATtools/scripts/main.py | 2 +- FATtools/scripts/mkvdisk.py | 1 - FATtools/scripts/wipe.py | 40 +++++++++++++++++++++++++++++++++++++ FATtools/version.py | 2 +- README.MD | 8 +++++++- 7 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 FATtools/scripts/wipe.py diff --git a/FATtools/FAT.py b/FATtools/FAT.py index df215b4..a647876 100644 --- a/FATtools/FAT.py +++ b/FATtools/FAT.py @@ -630,7 +630,6 @@ def free(self, start, runs=None): if self.last <= next <= self.last+7: break - class Chain(object): "Opens a cluster chain or run like a plain file" def __init__ (self, boot, fat, cluster, size=0, nofat=0, end=0): @@ -1392,6 +1391,18 @@ def getdiskspace(self): free_bytes = self.fat.free_clusters * self.boot.cluster return (self.fat.free_clusters, free_bytes) + def wipefreespace(self): + "Zeroes free clusters" + buf = (4<<20) * b'\x00' + fourmegs = (4<<20)//self.boot.cluster + for start, length in self.fat.free_clusters_map.items(): + if DEBUG&4: log("Wiping %d clusters from cluster #%d", length, start) + self.boot.stream.seek(self.boot.cl2offset(start)) + while length: + q = min(length, fourmegs) + self.boot.stream.write(buf[:q*self.boot.cluster]) + length -= q + def open(self, name): "Opens the chain corresponding to an existing file name" self._checkopen() diff --git a/FATtools/exFAT.py b/FATtools/exFAT.py index 12e71ac..c26918d 100644 --- a/FATtools/exFAT.py +++ b/FATtools/exFAT.py @@ -838,6 +838,18 @@ def getdiskspace(self): free_bytes = self.boot.bitmap.free_clusters * self.boot.cluster return (self.boot.bitmap.free_clusters, free_bytes) + def wipefreespace(self): + "Zeroes free clusters" + buf = (4<<20) * b'\x00' + fourmegs = (4<<20)//self.boot.cluster + for start, length in self.boot.bitmap.free_clusters_map.items(): + if DEBUG&4: log("Wiping %d clusters from cluster #%d", length, start) + self.boot.stream.seek(self.boot.cl2offset(start)) + while length: + q = min(length, fourmegs) + self.boot.stream.write(buf[:q*self.boot.cluster]) + length -= q + def open(self, name): "Opens the slot corresponding to an existing file name" self._checkopen() diff --git a/FATtools/scripts/main.py b/FATtools/scripts/main.py index 7df3bf8..bff1cfb 100644 --- a/FATtools/scripts/main.py +++ b/FATtools/scripts/main.py @@ -2,7 +2,7 @@ def main(): - scripts=["cat","cp","imgclone","ls","mkfat","mkvdisk","rm","reordergui"] + scripts=["cat","cp","imgclone","ls","mkfat","mkvdisk","rm","reordergui","wipe"] help_s="Usage: fattools " + ''.join( ['%s|'%s for s in scripts])[:-1] if len(sys.argv) < 2: diff --git a/FATtools/scripts/mkvdisk.py b/FATtools/scripts/mkvdisk.py index 63eb96f..e503b07 100644 --- a/FATtools/scripts/mkvdisk.py +++ b/FATtools/scripts/mkvdisk.py @@ -33,7 +33,6 @@ def call(args): if not args.image_size: print("mkvdisk error: you must specify a virtual disk image size!") - par.print_help() sys.exit(1) u = args.image_size[-1].lower() diff --git a/FATtools/scripts/wipe.py b/FATtools/scripts/wipe.py new file mode 100644 index 0000000..74c05c2 --- /dev/null +++ b/FATtools/scripts/wipe.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +import sys, os, argparse, logging +from FATtools import Volume + +DEBUG = 0 +from FATtools.debug import log + +#~ logging.basicConfig(level=logging.DEBUG, filename='wipe.log', filemode='w') + +class Arguments: + pass + + +def wipe(img): + v = Volume.vopen(img,'r+b') + if not hasattr(v, 'wipefreespace'): + print("Couldn't open a FAT/exFAT filesystem inside '%s'"%img) + sys.exit(1) + print('Wiping %d free clusters (%d bytes) . . .' % v.getdiskspace()) + v.wipefreespace() + print('Done.') + +def create_parser(parser_create_fn=argparse.ArgumentParser,parser_create_args=None): + help_s = """ + fattools wipe IMAGE_FILE + """ + par = parser_create_fn(*parser_create_args,usage=help_s, + formatter_class=argparse.RawDescriptionHelpFormatter, + description="Wipes the free space in an (ex)FAT formatted disk, zeroing all free clusters.", + epilog="Combined with imgclone tool, it permits to optimize a virtual disk image size.\n") + par.add_argument('image_file', nargs=1) + return par + +def call(args): + wipe(args.image_file[0]) + +if __name__ == '__main__': + par=create_parser() + args = par.parse_args() + call(args) diff --git a/FATtools/version.py b/FATtools/version.py index be7ea2b..a5ff605 100644 --- a/FATtools/version.py +++ b/FATtools/version.py @@ -1 +1 @@ -__version__ = '1.0.28' \ No newline at end of file +__version__ = '1.0.29' \ No newline at end of file diff --git a/README.MD b/README.MD index dbb0ab4..eb33db5 100644 --- a/README.MD +++ b/README.MD @@ -21,7 +21,7 @@ Following features are implemented (mostly in Python, with a few ctypes calls to - MBR and GPT partitions handling - Long File Name and Unicode support - tools to open, create, rename, list and delete files and directories, and to partition disks -- facilities to sort, clean and shrink directory tables +- facilities to sort, clean and shrink directory tables and to wipe (zero) free space - file fragmentation calculator - mkfat tool to properly (partition and) apply a FAT12/16/32 or exFAT filesystem to a block device (file or disk) and let CHKDSK be happy with it (included exFAT compressed Up-Case table generator) @@ -57,10 +57,16 @@ fattools mkfat -t exfat -p gpt image.vhdx fattools mkvdisk -b image.vdi delta.vdi ``` +- to wipe free space in an (ex)FAT formatted disk, zeroing all free clusters: +``` +fattools wipe image.vhd +``` + - to convert a RAW disk image into a Dynamic VHD (so implicitly virtualizing zeroed data blocks): ``` fattools imgclone image.raw image.vhd ``` +Please note that resulting image size can be reduced if the free space in image.raw has been wiped (zeroed) before. - to list contents in a disk image, copy items to/from it, display and erase them: ```