11import asyncio
22import csv
33import io
4+ import signal
45import sys
56import traceback
67from typing import Dict , Set , List , Optional , Any
910from .file_writers import FileWriters
1011
1112class MftAnalyzer :
12-
13- def __init__ (self , mft_file : str , output_file : str , debug : bool = False , very_debug : bool = False ,
14- verbosity : int = 0 , compute_hashes : bool = False , export_format : str = "csv" ) -> None :
13+ def __init__ (self , mft_file : str , output_file : str , debug : int = 0 , verbosity : int = 0 ,
14+ compute_hashes : bool = False , export_format : str = "csv" ) -> None :
1515 self .mft_file = mft_file
1616 self .output_file = output_file
1717 self .debug = debug
18- self .very_debug = very_debug
19- self .verbosity = verbosity
20- self .compute_hashes = compute_hashes
21- self .export_format = export_format
18+ self .verbosity = int (verbosity )
2219 self .compute_hashes = compute_hashes
2320 self .export_format = export_format
24- self .mft_records = {}
25- self .interrupt_flag = asyncio .Event ()
26-
2721 self .csvfile = None
2822 self .csv_writer = None
29-
23+ self .interrupt_flag = asyncio .Event ()
24+ self .setup_interrupt_handler ()
25+
26+ self .mft_records = {}
3027 self .stats = {
3128 'total_records' : 0 ,
3229 'active_records' : 0 ,
@@ -41,12 +38,26 @@ def __init__(self, mft_file: str, output_file: str, debug: bool = False, very_de
4138 'unique_crc32' : set (),
4239 })
4340
41+ def setup_interrupt_handler (self ):
42+ def interrupt_handler (signum , frame ):
43+ self .log ("Interrupt received. Cleaning up..." , 1 )
44+ self .interrupt_flag .set ()
45+
46+ if sys .platform == "win32" : # Windows is evil ...
47+ import win32api
48+ win32api .SetConsoleCtrlHandler (lambda x : interrupt_handler (None , None ), True )
49+
50+ else : # On a proper operating system ...
51+ signal .signal (signal .SIGINT , interrupt_handler )
52+ signal .signal (signal .SIGTERM , interrupt_handler )
53+
4454 def log (self , message : str , level : int = 0 ):
4555 if level <= self .debug or level <= self .verbosity :
4656 print (message )
4757
4858 async def analyze (self ) -> None :
4959 try :
60+ self .log ("Starting MFT analysis..." , 1 )
5061 self .initialize_csv_writer ()
5162 await self .process_mft ()
5263 await self .write_output ()
@@ -57,6 +68,10 @@ async def analyze(self) -> None:
5768 finally :
5869 if self .csvfile :
5970 self .csvfile .close ()
71+ if self .interrupt_flag .is_set ():
72+ self .log ("Analysis interrupted by user." , 1 )
73+ else :
74+ self .log ("Analysis complete." , 1 )
6075 self .print_statistics ()
6176
6277
@@ -65,12 +80,14 @@ async def process_mft(self) -> None:
6580 try :
6681 with open (self .mft_file , 'rb' ) as f :
6782 while not self .interrupt_flag .is_set ():
68- raw_record = f . read ( MFT_RECORD_SIZE )
83+ raw_record = await self . read_record ( f )
6984 if not raw_record :
7085 break
7186
7287 try :
88+ self .log (f"Processing record { self .stats ['total_records' ]} " , 2 )
7389 record = MftRecord (raw_record , self .compute_hashes )
90+ self .log (f"Record parsed, recordnum: { record .recordnum } " , 2 )
7491 self .stats ['total_records' ] += 1
7592
7693 if record .flags & FILE_RECORD_IN_USE :
@@ -90,9 +107,14 @@ async def process_mft(self) -> None:
90107 if self .stats ['total_records' ] % 1000 == 0 :
91108 await self .write_csv_block ()
92109 self .mft_records .clear ()
110+
111+ if self .interrupt_flag .is_set ():
112+ self .log ("Interrupt detected. Stopping processing." , 1 )
113+ break
93114
94115 except Exception as e :
95116 self .log (f"Error processing record { self .stats ['total_records' ]} : { str (e )} " , 1 )
117+ self .log (f"Raw record (first 100 bytes): { raw_record [:100 ].hex ()} " , 2 )
96118 if self .debug >= 2 :
97119 traceback .print_exc ()
98120 continue
@@ -104,6 +126,8 @@ async def process_mft(self) -> None:
104126
105127 self .log (f"MFT processing complete. Total records processed: { self .stats ['total_records' ]} " , 0 )
106128
129+ async def read_record (self , file ):
130+ return file .read (MFT_RECORD_SIZE )
107131
108132 def handle_interrupt (self ) -> None :
109133 if sys .platform == "win32" :
@@ -146,19 +170,19 @@ async def write_csv_block(self) -> None:
146170 csv_row = [str (item ) for item in csv_row ]
147171
148172 self .csv_writer .writerow (csv_row )
149- if self .very_debug :
173+ if self .debug :
150174 self .log (f"Wrote record { record .recordnum } to CSV" , 2 )
151175 except Exception as e :
152176 self .log (f"Error writing record { record .recordnum } : { str (e )} " , 1 )
153- if self .very_debug :
177+ if self .debug :
154178 traceback .print_exc ()
155179
156180 if self .csvfile :
157181 self .csvfile .flush ()
158182 self .log (f"CSV block written. Current file size: { self .csvfile .tell () if self .csvfile else 0 } bytes" , 2 )
159183 except Exception as e :
160184 self .log (f"Error in write_csv_block: { str (e )} " , 0 )
161- if self .debug or self . very_debug :
185+ if self .debug :
162186 traceback .print_exc ()
163187
164188
@@ -222,4 +246,10 @@ async def write_output(self) -> None:
222246 elif self .export_format == "excel" :
223247 await FileWriters .write_excel (list (self .mft_records .values ()), self .output_file )
224248 else :
225- print (f"Unsupported export format: { self .export_format } " )
249+ print (f"Unsupported export format: { self .export_format } " )
250+
251+ async def cleanup (self ):
252+ self .log ("Performing cleanup..." , 1 )
253+ # to-do add more cleanup after database stuff is integrated.
254+ await self .write_remaining_records ()
255+ self .log ("Cleanup complete." , 1 )
0 commit comments