1 /*****************************************************************************
2 
3   Unfrag
4 
5 *****************************************************************************/
6 
7 
8 #include "Unfrag.h"
9 #include "DriveVolume.h"
10 #include "Defragment.h"
11 #include <process.h>
12 
13 
14 bool QuietMode = false;
15 bool VerboseMode = false;
16 
17 
18 // Makes sure we're in Windows 2000
19 bool CheckWinVer (void)
20 {
21     OSVERSIONINFO OSVersion;
22 
23     ZeroMemory (&OSVersion, sizeof (OSVersion));
24     OSVersion.dwOSVersionInfoSize = sizeof (OSVersion);
25     GetVersionEx (&OSVersion);
26 
27     // Need Windows 2000!
28 
29     // Check for NT first
30     // Actually what we do is check that weLL're not on Win31+Win32s and that we're
31     // not in Windows 9x. It's possible that there could be more Windows "platforms"
32     // in the future and there's no sense in claiming incompatibility.
33     if (OSVersion.dwPlatformId == VER_PLATFORM_WIN32s  ||
34         OSVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
35     {
36         return (false);
37     }
38 
39     // Ok weLL're in Windows NT, now make sure we're in 2000
40     if (OSVersion.dwMajorVersion < 5)
41         return (false);
42 
43     // Kew, we're in at least Windows 2000 ("NT 5.0")
44     return (true);
45 }
46 
47 
48 wchar_t *AddCommas (wchar_t *Result, uint64 Number)
49 {
50 	wchar_t  Temp[128];
51 	int   TempLen;
52 	//wchar_t *p = NULL;
53 	int   AddCommas = 0;
54 	wchar_t *StrPosResult = NULL;
55 	wchar_t *StrPosOrig = NULL;
56 
57 	// we get the string form of the number, then we count down w/ AddCommas
58 	// while copying the string from Temp1 to Result. when AddCommas % 3  == 1,
59 	// slap in a commas as well, before the #.
60 	swprintf (Temp, L"%I64u", Number);
61 	AddCommas = TempLen = wcslen (Temp);
62 	StrPosOrig   = Temp;
63 	StrPosResult = Result;
64 	while (AddCommas)
65 	{
66 		if ((AddCommas % 3) == 0  &&  AddCommas != TempLen) // avoid stuff like ",345"
67 		{
68 			*StrPosResult = L',';
69 			StrPosResult++;
70 		}
71 
72 		*StrPosResult = *StrPosOrig;
73 		StrPosResult++;
74 		StrPosOrig++;
75 
76 		*StrPosResult = 0;
77 
78 		AddCommas--;
79 	}
80 
81 	return (Result);
82 }
83 
84 
85 void PrintBanner (void)
86 {
87     wprintf (L"%s v%s\n", APPNAME_CLI, APPVER_STR);
88     wprintf (L"%s\n", APPCOPYRIGHT);
89     wprintf (L"\n");
90 
91     return;
92 }
93 
94 
95 void FraggerHelp (void)
96 {
97     wprintf (L"Usage: unfrag drive: [...] <-f | -e>\n");
98     wprintf (L"\n");
99     wprintf (L"drive:  : The drive to defrag. Should be two characters long, ie 'c:' or 'd:'.\n");
100     wprintf (L"          Multiple drives may be given, and all will be simultaneously\n");
101     wprintf (L"          defragmented using the same options.\n");
102     wprintf (L"-f      : Do a fast defragmentation. Files that are not fragmented will not be\n");
103     wprintf (L"          moved. Only one pass is made over the file list. Using this option\n");
104     wprintf (L"          may result in not all files being defragmented, depending on\n");
105     wprintf (L"          available disk space.\n");
106     wprintf (L"-e      : Do an extensive defragmention. Files will be moved in an attempt to\n");
107     wprintf (L"          defragment both files and free space.\n");
108 
109     if (!CheckWinVer())
110     {
111         wprintf (L"\n");
112         wprintf (L"NOTE: This program requires Windows 2000, which is not presently running on\n");
113         wprintf (L"this system.\n");
114     }
115 
116     return;
117 }
118 
119 
120 void __cdecl DefragThread (LPVOID parm)
121 {
122     Defragment *Defrag;
123 
124     Defrag = (Defragment *) parm;
125     Defrag->Start ();
126 
127     _endthread ();
128     return;
129 }
130 
131 
132 Defragment *StartDefragThread (wstring Drive, DefragType Method, HANDLE &Handle)
133 {
134     Defragment *Defragger;
135     unsigned long Thread;
136 
137     Defragger = new Defragment (Drive, Method);
138     //Thread = /*CreateThread*/ _beginthreadex (NULL, 0, DefragThread, Defragger, 0, &ThreadID);
139     Thread = _beginthread (DefragThread, 0, Defragger);
140     Handle = *((HANDLE *)&Thread);
141     return (Defragger);
142 }
143 
144 #ifdef _CUI_
145 // Main Initialization
146 extern "C" int wmain (int argc, wchar_t **argv)
147 {
148     vector<wstring>       Drives;
149     vector<Defragment *> Defrags;
150     DefragType           DefragMode = DefragInvalid;
151 
152     PrintBanner ();
153 
154     // Parse command line arguments
155     bool ValidCmdLine = false;
156     for (int c = 0; c < argc; c++)
157     {
158         if (wcslen(argv[c]) == 2  &&  argv[c][1] == L':')
159         {
160             Drives.push_back (_wcsupr(argv[c]));
161         }
162         else
163         if ((argv[c][0] == L'-'  ||  argv[c][0] == L'/')  &&  wcslen(argv[c]) == 2)
164         {
165             switch (tolower(argv[c][1]))
166             {
167                 case L'?' :
168                 case L'h' :
169                     FraggerHelp ();
170                     return (0);
171 
172                 case L'f' :
173                     if (DefragMode != DefragInvalid)
174                     {
175                         ValidCmdLine = false;
176                         break;
177                     }
178                     DefragMode = DefragFast;
179                     ValidCmdLine = true;
180                     break;
181 
182                 case L'e' :
183                     if (DefragMode != DefragInvalid)
184                     {
185                         ValidCmdLine = false;
186                         break;
187                     }
188                     DefragMode = DefragExtensive;
189                     ValidCmdLine = true;
190                     break;
191 
192             }
193         }
194     }
195 
196     if (DefragMode == DefragInvalid)
197         ValidCmdLine = false;
198 
199     if (!ValidCmdLine)
200     {
201         wprintf (L"Invalid command-line options. Use '%s -?' for help.\n", argv[0]);
202         return (0);
203     }
204 
205     // Check OS requirements
206     if (!CheckWinVer())
207     {
208         wprintf (L"Fatal Error: This program requires Windows 2000.\n");
209         return (0);
210     }
211 
212 	for (size_t d = 0; d < Drives.size (); d++)
213     {
214         HANDLE TossMe;
215         Defrags.push_back (StartDefragThread (Drives[d], DefragMode, TossMe));
216     }
217 
218     for (size_t d = 0; d < Drives.size () - 1; d++)
219         wprintf (L"\n ");
220 
221     bool Continue = true;
222     HANDLE Screen;
223 
224     Screen = GetStdHandle (STD_OUTPUT_HANDLE);
225     while (Continue)
226     {
227         Sleep (25);
228 
229         // Get current screen coords
230         CONSOLE_SCREEN_BUFFER_INFO ScreenInfo;
231 
232         GetConsoleScreenBufferInfo (Screen, &ScreenInfo);
233 
234         // Now set back to the beginning
235         ScreenInfo.dwCursorPosition.X = 0;
236         ScreenInfo.dwCursorPosition.Y -= Drives.size();
237         SetConsoleCursorPosition (Screen, ScreenInfo.dwCursorPosition);
238 
239         for (size_t d = 0; d < Drives.size (); d++)
240         {
241             wprintf (L"\n%6.2f%% %-70s", Defrags[d]->GetStatusPercent(), Defrags[d]->GetStatusString().c_str());
242         }
243 
244         // Determine if we should keep going
245         Continue = false;
246         for (size_t d = 0; d < Drives.size (); d++)
247         {
248             if (!Defrags[d]->IsDoneYet()  &&  !Defrags[d]->HasError())
249                 Continue = true;
250         }
251     }
252 
253 #if 0
254     // Loop through the drives list
255     for (int d = 0; d < Drives.size(); d++)
256     {
257         DriveVolume *Drive;
258 
259         Drive = new DriveVolume;
260 
261         // First thing: build a file list.
262         wprintf (L"Opening volume %s ...", Drives[d].c_str());
263         if (!Drive->Open (Drives[d]))
264         {
265             wprintf (L"FAILED\n\n");
266             delete Drive;
267             continue;
268         }
269         wprintf (L"\n");
270 
271         wprintf (L"    Getting drive bitmap ...");
272         if (!Drive->GetBitmap ())
273         {
274             wprintf (L"FAILED\n\n");
275             delete Drive;
276             continue;
277         }
278         wprintf (L"\n");
279 
280         wprintf (L"    Obtaining drive geometry ...");
281         if (!Drive->ObtainInfo ())
282         {
283             wprintf (L"FAILED\n\n");
284             delete Drive;
285             continue;
286         }
287         wprintf (L"\n");
288 
289         wprintf (L"    Building file database for drive %s ...", Drives[d].c_str());
290         if (!Drive->BuildFileList ())
291         {
292             wprintf (L"FAILED\n\n");
293             delete Drive;
294             continue;
295         }
296         wprintf (L"\n");
297 
298         wprintf (L"    %u files\n", Drive->GetDBFileCount ());
299 
300         // Analyze only?
301         if (DefragMode == DefragAnalyze)
302         {
303             uint64 UsedBytes  = 0;  // total bytes used, with cluster size considerations
304             uint64 TotalBytes = 0;  // total bytes used
305             uint64 SlackBytes = 0;  // wasted space due to slack
306             uint32 Fragged    = 0;  // fragmented files
307 
308             wprintf (L"    Analyzing ...");
309             if (VerboseMode)
310                 wprintf (L"\n");
311 
312             for (int i = 0; i < Drive->GetDBFileCount(); i++)
313             {
314                 uint64 Used;
315                 uint64 Slack;
316                 FileInfo Info;
317 
318                 Info = Drive->GetDBFile (i);
319 
320                 // Compute total used disk space
321                 Used = ((Info.Size + Drive->GetClusterSize() - 1) / Drive->GetClusterSize()) * Drive->GetClusterSize();
322                 Slack = Used - Info.Size;
323 
324                 UsedBytes += Used;
325                 SlackBytes += Slack;
326                 TotalBytes += Info.Size;
327 
328                 if (VerboseMode)
329                 {
330                     wprintf (L"    %s%s, ", Drive->GetDBDir (Info.DirIndice).c_str(), Info.Name.c_str());
331 
332                     if (Info.Attributes.AccessDenied)
333                         wprintf (L"access was denied\n");
334                     else
335                     {
336                         if (Info.Attributes.Unmovable == 1)
337                             wprintf (L"unmovable, ");
338 
339                         wprintf (L"%I64u bytes, %I64u bytes on disk, %I64u bytes slack, %u fragments\n",
340                             Info.Size, Used, Slack, Info.Fragments.size());
341                     }
342                 }
343 
344                 if (Info.Fragments.size() > 1)
345                     Fragged++;
346             }
347 
348             if (!VerboseMode)
349                 wprintf (L"\n");
350 
351             // TODO: Make it not look like ass
352             wprintf (L"\n");
353             wprintf (L"    Overall Analysis\n");
354             wprintf (L"    ----------------\n");
355             wprintf (L"    %u clusters\n", Drive->GetClusterCount ());
356             wprintf (L"    %u bytes per cluster\n", Drive->GetClusterSize());
357             wprintf (L"    %I64u total bytes on drive\n", (uint64)Drive->GetClusterCount() * (uint64)Drive->GetClusterSize());
358             wprintf (L"\n");
359             wprintf (L"    %u files\n", Drive->GetDBFileCount ());
360             wprintf (L"    %u contiguous files\n", Drive->GetDBFileCount () - Fragged);
361             wprintf (L"    %u fragmented files\n", Fragged);
362             wprintf (L"\n");
363             wprintf (L"    %I64u bytes\n", TotalBytes);
364             wprintf (L"    %I64u bytes on disk\n", UsedBytes);
365             wprintf (L"    %I64u bytes slack\n", SlackBytes);
366         }
367 
368         // Fast defragment!
369         if (DefragMode == DefragFast  ||  DefragMode == DefragExtensive)
370         {
371             uint32 i;
372             uint64 FirstFreeLCN;
373             wchar_t PrintName[80];
374             int Width = 66;
375 
376             if (DefragMode == DefragFast)
377                 wprintf (L"    Performing fast file defragmentation ...\n");
378             else
379             if (DefragMode == DefragExtensive)
380                 wprintf (L"    Performing extensive file defragmentation\n");
381 
382             // Find first free LCN for speedier searches ...
383             Drive->FindFreeRange (0, 1, FirstFreeLCN);
384 
385             for (i = 0; i < Drive->GetDBFileCount(); i++)
386             {
387                 FileInfo Info;
388                 bool Result;
389                 uint64 TargetLCN;
390 
391                 wprintf (L"\r");
392 
393                 Info = Drive->GetDBFile (i);
394 
395                 FitName (PrintName, Drive->GetDBDir (Info.DirIndice).c_str(), Info.Name.c_str(), Width);
396                 wprintf (L"    %6.2f%% %-66s", (float)i / (float)Drive->GetDBFileCount() * 100.0f, PrintName);
397 
398                 // Can't defrag 0 byte files :)
399                 if (Info.Fragments.size() == 0)
400                     continue;
401 
402                 // If doing fast defrag, skip non-fragmented files
403                 if (Info.Fragments.size() == 1  &&  DefragMode == DefragFast)
404                     continue;
405 
406                 // Find a place that can fit the file
407                 Result = Drive->FindFreeRange (FirstFreeLCN, Info.Clusters, TargetLCN);
408 
409                 // If we're doing an extensive defrag and the file is already defragmented
410                 // and if its new location would be after its current location, don't
411                 // move it.
412                 if (DefragMode == DefragExtensive  &&  Info.Fragments.size() == 1)
413                 {
414                     if (TargetLCN > Info.Fragments[0].StartLCN)
415                         continue;
416                 }
417 
418                 // Otherwise, defrag0rize it!
419                 if (Result)
420                 {
421                     bool Success = false;
422 
423                     if (Drive->MoveFileDumb (i, TargetLCN))
424                         Success = true;
425                     else
426                     {   // hmm, look for another area to move it to
427                         Result = Drive->FindFreeRange (TargetLCN + 1, Info.Clusters, TargetLCN);
428                         if (Result)
429                         {
430                             if (Drive->MoveFileDumb (i, TargetLCN))
431                                 Success = true;
432                             else
433                             {   // Try updating the drive bitmap
434                                 if (Drive->GetBitmap ())
435                                 {
436                                     Result = Drive->FindFreeRange (0, Info.Clusters, TargetLCN);
437                                     if (Result)
438                                     {
439                                         if (Drive->MoveFileDumb (i, TargetLCN))
440                                             Success = true;
441                                     }
442                                 }
443                             }
444                         }
445                     }
446 
447                     if (!Success)
448                         wprintf (L"\n        -> failed\n");
449 
450                     Drive->FindFreeRange (0, 1, FirstFreeLCN);
451                 }
452             }
453 
454             wprintf (L"\n");
455         }
456         wprintf (L"Closing volume %s ...", Drives[d].c_str());
457         delete Drive;
458         wprintf (L"\n");
459     }
460 #endif
461 
462     return (0);
463 }
464 #endif
465