Здравствуйте, братья программисты! Писал я программу, и возникли у меня два вопроса.
Вопрос первый: Как. Сделать так, чтобы программа выполняла функцию, наподобие функции "анализа" на фрагментацию диска в программе дефрагментации.
Вопрос второй: Как определить скорость обращения винды к файлам.
Заранее говорю вам огромное СПАСИБО!
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
09-06-2006 06:06
По вопросу фрагментации. Я решал такую проблему
Программа дефрагментации - defrag.exe (в ХР точно, в 98 - не проверял)
Использование:
defrag <том> [-a] [-f] [-v] [-?]
том Буква диска, или точка подключения (например, d: или d:\vol\mpoint)
-a Только анализ
-f Дефрагментация даже при ограниченном месте на диске
-v Подробные результаты
-? Вывод справки
Нужно запускать отдельным процессом с параметрами -а и -v. Для этого использовал компонент JcCreateProcess из JVCL, из него можно вытянуть результат выполнения программы. Получаешь и обрабатываешь :)
Правда, результат выдается в кодировке DOS, но найти модуль перекодирования не проблема. Ну и соответственно время работы определения степени фрагментации будет зависеть от размера дисков - нужно подождать
З.Ы. Гасите на время работы касперского и прочую лабуду - фиг дождетесь окончания работы процесса (у меня терпения не хватало)
function CTL_CODE(DeviceType, Func, Method, Access: uint): uint;
begin
result := ((DeviceType) shl 16) or ((Access) shl 14) or ((Func) shl 2) or (Method)
end;
var lpinbuf: ^STARTING_LCN_INPUT_BUFFER;
hDevice: Cardinal;
i: Cardinal;
lpBytesReturned: PDWORD;
j: byte;
r: LongBool;
FSCTL_GET_VOLUME_BITMAP: Cardinal;
base: ^VOLUME_BITMAP_BUFFER;
begin
// Get a handle to our device
hDevice := CreateFile(PChar('\\.\C:'),GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,OPEN_EXISTING,0,0);
// Check error
if hDevice = INVALID_HANDLE_VALUE then exit;
// Allocate memory and other initialization
GetMem(base, sizeof(VOLUME_BITMAP_BUFFER)+1024*1024*40);
GetMem(lpinbuf, sizeof(STARTING_LCN_INPUT_BUFFER));
GetMem(lpBytesReturned, sizeof(DWORD));
lpinbuf.StartingLcn.QuadPart := 0;
FSCTL_GET_VOLUME_BITMAP := CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 27, METHOD_NEITHER, FILE_ANY_ACCESS);
// Make a query to device driver
r := DeviceIOControl(hDevice, FSCTL_GET_VOLUME_BITMAP, lpinbuf, sizeof(STARTING_LCN_INPUT_BUFFER), base,
sizeof(VOLUME_BITMAP_BUFFER)+1024*1024*40, lpBytesReturned, nil);
if (r) then
begin
WriteLn('Query successful, we have:');
for i := 0 to lpBytesReturned^ do
begin
for j := 0 to 7 do
begin
if odd(base^.Buffer[i] shr j) then write('*') else write('.');
end;
end;
end;
// Free memory and other finalization
CloseHandle(hDevice);
FreeMem(base, sizeof(VOLUME_BITMAP_BUFFER)+1024*1024*40);
FreeMem(lpinbuf, sizeof(STARTING_LCN_INPUT_BUFFER));
FreeMem(lpBytesReturned, sizeof(DWORD));
// Wait for Enter
readln;
end.
Если вы читаете Си, то могу дать сорц дефрагментатора
/*
Defrag -- Defragment and optimize all harddisks.
USAGE
This program is exactly the same as the "WinDefrag" progam, but is
a commandline program. It is therefore a bit faster and uses a bit
less memory.
The program was designed to run automatically every day by the Task
Scheduler, but you can also start it by hand. Start the program and
it will automatically detect all harddisks in the computer and
defragment them all. The program can also optimize the harddisks,
moving all the files close together to at the beginning of the
harddisk (the "-o" option).
You can safely stop the utility at any time, for example with CTRL-C
or by closing the window. If the program is moving a big file it will
finish moving before terminating. The program is totally safe, your
harddisk cannot be corrupted, even if for example you suddenly turn
of the power of the computer.
CPU useage will not be noticeable unless a lot of very small files
need to be moved. The memory footprint is very small, about 600Kb
plus on average 100 bytes for every file on the harddisk.
defrag [-o] [-o1] [-o2] [-d N] [-h] [volume]
-o Do not optimize the volume(s), only defragment.
-o1 [default] Fast optimization: move files down.
-o2 Slow optimization: move holes up.
-d N Set debug level to N. Default is 0, highest is 5.
-h Show help.
volume For example 'C:'. Default is all volumes.
INSTALLATION
No installation or configuration necessary, the program is ready
to run.
The utility is designed to be started automatically by the Task
Scheduler. Add it to the scheduled tasks like this:
- Start -> Control Panel -> Scheduled Tasks -> Add Scheduled Task
- The wizard starts, click 'Next'.
- Use the 'browse' button to select the 'defrag.exe' program.
- Select 'daily', next, select a time, next.
- Enter a userid/password with administrator privileges, click 'Finish'.
DESCRIPTION
The program performs the following tasks:
- For all non-removable diskdrives:
- Defragment:
- For all the files:
- Test if the file is fragmented by looking at it's cluster
map.
- If fragmented then find the smallest hole anywhere on disk
in which the entire file fits and move the file.
- Store the file in a list in memory.
- Optimize, move all files to the beginning of the harddisk:
- Look for empty blocks on the harddisk.
- Find the largest file or directory above the empty block that
will fit, and move it. Directories are preferred over files.
- If no file/dir was found to fit the empty block (all files/dirs
are larger) then try to shift the file/dir just above the empty
block down. This has the effect of moving the empty block
upward. By repeating the process the empty block will
eventually join up with the next empty block.
- If the next file/dir could not be shifted down, then try moving
it to another location on disk. This will enlarge the
empty block.
The program uses the standard Microsoft functions for defragmenting
harddisks, FSCTL_GET_RETRIEVAL_POINTERS, FSCTL_GET_VOLUME_BITMAP,
and FSCTL_MOVE_FILE. I have never experienced data loss or data
corruption, so that's allright, but the functions have severe
limitations and even faults. Theoretically speaking the utility
should be able to produce a perfectly defragmented and optimized
harddisk every time, but the problems in the Microsoft calls can
fragment files that were unfragmented, and leave empty blocks
unfilled.
#define USAGE "\n%s [-o] [-o1] [-o2] [-d N] [-h] [volume]\n-o Do not optimize the volume(s), only defragment.\n-o1 [default] Fast optimization: move files down.\n-o2 Slow optimization: move holes up.\n-d N Set debug level to N. Default is 0, highest is 5.\n-h Show this help.\nvolume For example 'C:'. Default is all volumes.\n"
#define NO 0
#define YES 1
#define MAXULONG64 18446744073709551615
/* Some glue stuff, used by the Windows version. */
#define COLORREF int
COLORREF ColorEmpty;
COLORREF ColorAllocated;
COLORREF ColorUnfragmented;
COLORREF ColorUnmovable;
COLORREF ColorFragmented;
COLORREF ColorBusy;
/* Translate character to lowercase. */
char LowerCase(char c) {
if ((c >= 'A') && (c <= 'Z')) return((c - 'A') + 'a');
return(c);
}
/* Compare a string with a mask, case-insensitive. If it matches then return
YES, otherwise NO. The mask may contain wildcard characters '?' (any
character) '*' (any characters). */
int MatchMask(char *String, char *Mask) {
char *m;
char *s;
if (String == NULL) return NO; /* Just to speed up things. */
if (Mask == NULL) return NO;
if (strcmp(Mask,"*") == 0) return YES;
m = Mask;
s = String;
while ((*m != '\0') && (*s != '\0')) {
if ((LowerCase(*m) != LowerCase(*s)) && (*m != '?')) {
if (*m != '*') return NO;
m++;
if (*m == '\0') return YES;
while (*s != '\0') {
if (MatchMask(s,m) == YES) return YES;
s++;
}
return NO;
}
m++;
s++;
}
while (*m == '*') m++;
if ((*s == '\0') && (*m == '\0')) return YES;
return NO;
}
/* Return YES if the file is a system file. This is only used to suppress
messages. */
int IsSystemFile(char *FileName) {
if ((FileName == NULL) || (*FileName == '\0')) return(NO);
if (MatchMask(FileName,"*\\pagefile.sys") == YES) return(YES);
if (MatchMask(FileName,"*\\System Volume Information") == YES) return(YES);
if (MatchMask(FileName,"*\\ntuser.dat") == YES) return(YES);
if (MatchMask(FileName,"*\\ntuser.dat.log") == YES) return(YES);
if (MatchMask(FileName,"*\\system32\\config\\*") == YES) return(YES);
return(NO);
}
/* Return pointer to the first item in the tree (the first file on disk). */
struct FileListStruct *TreeSmallest(struct FileListStruct *Top) {
if (Top == NULL) return(NULL);
while (Top->Smaller != NULL) Top = Top->Smaller;
return(Top);
}
/* Return pointer to the last item in the tree (the last file on disk). */
struct FileListStruct *TreeBiggest(struct FileListStruct *Top) {
if (Top == NULL) return(NULL);
while (Top->Bigger != NULL) Top = Top->Bigger;
return(Top);
}
/* Return pointer to the next item in the tree. */
struct FileListStruct *TreePrev(struct FileListStruct *Here) {
struct FileListStruct *Temp;
if (Here == NULL) return(Here);
if (Here->Smaller != NULL) {
Here = Here->Smaller;
while (Here->Bigger != NULL) Here = Here->Bigger;
return(Here);
}
do {
Temp = Here;
Here = Here->Parent;
} while ((Here != NULL) && (Here->Smaller == Temp));
return(Here);
}
/* Return pointer to the next item in the tree. */
struct FileListStruct *TreeNext(struct FileListStruct *Here) {
struct FileListStruct *Temp;
if (Here == NULL) return(NULL);
if (Here->Bigger != NULL) {
Here = Here->Bigger;
while (Here->Smaller != NULL) Here = Here->Smaller;
return(Here);
}
do {
Temp = Here;
Here = Here->Parent;
} while ((Here != NULL) && (Here->Bigger == Temp));
return(Here);
}
/* Insert a record into the tree. The tree is sorted by LCN (Logical
Cluster Number). */
void TreeInsert(struct FileListStruct *New, struct FileListStruct **Top) {
struct FileListStruct *Here;
struct FileListStruct *Ins;
int Found;
if (New == NULL) return;
/* Locate the place where the record should be inserted. */
Here = *Top;
Ins = NULL;
Found = 1;
while (Here != NULL) {
Ins = Here;
Found = 0;
if (Here->Lcn > New->Lcn) {
Found = 1;
Here = Here->Smaller;
} else {
if (Here->Lcn < New->Lcn) Found = -1;
Here = Here->Bigger;
}
}
/* Add a file to the FileList in memory. The list is used to find
files that can be moved to fill a hole. Return pointer to the
item added, or NULL if an error occurred (out of memory). */
struct FileListStruct *AddToFileList(
char *FileName,
ULONG64 Bytes,
ULONG64 Lcn,
DWORD RealClusters,
DWORD UncompressedClusters,
int Directory,
FILETIME Time) {
struct FileListStruct *New;
/* Create new record. */
New = (struct FileListStruct *)malloc(sizeof(struct FileListStruct));
if (New == NULL) return(NULL);
New->FileName = (char *)malloc(strlen(FileName) + 1);
if (New->FileName == NULL) return(NULL);
strcpy(New->FileName,FileName);
New->Bytes = Bytes;
New->Lcn = Lcn;
New->RealClusters = RealClusters;
New->UncompressedClusters = UncompressedClusters;
New->Directory = Directory;
New->Time.LowPart = Time.dwLowDateTime;
New->Time.HighPart = Time.dwHighDateTime;
/* Add record to the tree. */
TreeInsert(New,&FileList);
return(New);
}
if (Item->Bigger == NULL) {
/* It is trivial to delete a node with no Bigger child. We replace
the pointer leading to the node by it's Smaller child. In
other words, we replace the deleted node by its Smaller child. */
if (Item->Parent != NULL) {
if (Item->Parent->Smaller == Item) {
Item->Parent->Smaller = Item->Smaller;
} else {
Item->Parent->Bigger = Item->Smaller;
}
} else {
*Top = Item->Smaller;
}
if (Item->Smaller != NULL) Item->Smaller->Parent = Item->Parent;
} else if (Item->Bigger->Smaller == NULL) {
/* The Bigger child has no Smaller child. In this case, we move Bigger
into the node's place, attaching the node's Smaller subtree as the
new Smaller. */
if (Item->Parent != NULL) {
if (Item->Parent->Smaller == Item) {
Item->Parent->Smaller = Item->Bigger;
} else {
Item->Parent->Bigger = Item->Bigger;
}
} else {
*Top = Item->Bigger;
}
Item->Bigger->Parent = Item->Parent;
Item->Bigger->Smaller = Item->Smaller;
if (Item->Smaller != NULL) Item->Smaller->Parent = Item->Bigger;
} else {
/* Replace the node by it's inorder successor, that is, the node with
the smallest value greater than the node. We know it exists because
otherwise this would be case 1 or case 2, and it cannot have a Smaller
value because that would be the node itself. The successor can
therefore be detached and can be used to replace the node. */
/* Find the inorder successor. */
B = Item->Bigger;
while (B->Smaller != NULL) B = B->Smaller;
/* Detach the successor. */
if (B->Parent != NULL) {
if (B->Parent->Bigger == B) {
B->Parent->Bigger = B->Bigger;
} else {
B->Parent->Smaller = B->Bigger;
}
}
if (B->Bigger != NULL) B->Bigger->Parent = B->Parent;
/* Replace the node with the successor. */
if (Item->Parent != NULL) {
if (Item->Parent->Smaller == Item) {
Item->Parent->Smaller = B;
} else {
Item->Parent->Bigger = B;
}
} else {
*Top = B;
}
B->Parent = Item->Parent;
B->Smaller = Item->Smaller;
if (B->Smaller != NULL) B->Smaller->Parent = B;
B->Bigger = Item->Bigger;
if (B->Bigger != NULL) B->Bigger->Parent = B;
}
}
/* Delete a file from the list in memory. */
void DeleteFromFileList(struct FileListStruct *This) {
if ((FileList == NULL) || (This == NULL)) return;
TreeDetach(This,&FileList);
free(This->FileName);
free(This);
}
/* Delete the entire FileList */
void DeleteFileList(struct FileListStruct *Top) {
if (Top == NULL) return;
if (Top->Smaller != NULL) DeleteFileList(Top->Smaller);
if (Top->Bigger != NULL) DeleteFileList(Top->Bigger);
if (Top->FileName != NULL) free(Top->FileName);
free(Top);
}
/* Balance a tree.
It's difficult to explain what exactly happens here. For an excellent
tutorial see: http://www.stanford.edu/~blp/avl/libavl.html/Balancing-a-BST.html
*/
struct FileListStruct *TreeBalance(struct FileListStruct *Top) {
struct FileListStruct *A;
struct FileListStruct *B;
struct FileListStruct *C;
long Count;
long Skip;
if (Top == NULL) return(NULL);
BalanceCount = 0;
/* Convert the tree into a vine. */
A = Top;
C = A;
Count = 0;
while (A != NULL) {
/* If A has no Bigger child then move down the tree. */
if (A->Bigger == NULL) {
Count = Count + 1;
C = A;
A = A->Smaller;
continue;
}
/* Rotate left at A. */
B = A->Bigger;
if (Top == A) Top = B;
A->Bigger = B->Smaller;
if (A->Bigger != NULL) A->Bigger->Parent = A;
B->Parent = A->Parent;
if (B->Parent != NULL) {
if (B->Parent->Smaller == A) {
B->Parent->Smaller = B;
} else {
A->Parent->Bigger = B;
}
}
B->Smaller = A;
A->Parent = B;
/* Do again. */
A = B;
}
/* Calculate the number of skips. */
Skip = 1;
while (Skip < Count + 2) Skip = (Skip << 1);
Skip = Count + 1 - (Skip >> 1);
/* Compress the tree. */
while (C != NULL) {
if (Skip <= 0) C = C->Parent;
A = C;
while (A != NULL) {
B = A;
A = A->Parent;
if (A == NULL) break;
/* Rotate right at A. */
if (Top == A) Top = B;
A->Smaller = B->Bigger;
if (A->Smaller != NULL) A->Smaller->Parent = A;
B->Parent = A->Parent;
if (B->Parent != NULL) {
if (B->Parent->Smaller == A) {
B->Parent->Smaller = B;
} else {
B->Parent->Bigger = B;
}
}
A->Parent = B;
B->Bigger = A;
/* Next item. */
A = B->Parent;
/* If there were skips then leave if all done. */
Skip = Skip - 1;
if (Skip == 0) break;
}
}
/* Return the new top of the tree. */
return(Top);
}
/* Find the file that begins at the Lcn in the list in memory and return
the pointer to the entry. If not found then return NULL. */
struct FileListStruct *FindFileAtLcn(ULONG64 Lcn) {
struct FileListStruct *File;
/* Look in the FileList and return the file that best fits inside the free
cluster. Higher files get preference. Return a pointer to the item in the
FileList, or NULL if no file could be found. If PerfectFit=YES then only
return a file if it can be combined with other files to completely fill
the free block. */
struct FileListStruct *FindBestFile(
ULONG64 ClusterStart,
ULONG64 ClusterEnd,
int PerfectFit,
struct FileListStruct *IgnoreFile,
LONG64 MaxTime,
int Depth) {
struct FileListStruct *Here;
struct FileListStruct *BestFit;
struct FileListStruct *SmallerThan;
char s1[BUFSIZ];
struct __timeb64 Time;
ULONG64 TotalSize;
if (PerfectFit == YES) {
sprintf(s1,"Looking for perfect-fit %I64d[%I64d]",ClusterStart,
ClusterEnd - ClusterStart);
} else {
sprintf(s1,"Looking for best-fit %I64d[%I64d]",ClusterStart,
ClusterEnd - ClusterStart);
}
ShowDebugMessage(4,NULL,s1);
SmallerThan = NULL;
while (Running == RUNNING) {
/* Walk backwards through all the items on disk and select the largest
file that fits inside the free block. If we find an exact match then
immediately return it. */
BestFit = NULL;
TotalSize = 0;
for (Here = TreeBiggest(FileList); Here != NULL; Here = TreePrev(Here)) {
if (Here->Lcn < ClusterEnd) break;
TotalSize = TotalSize + Here->RealClusters;
if ((IgnoreFile != NULL) && (Here == IgnoreFile)) continue;
if ((SmallerThan != NULL) &&
(Here->RealClusters >= SmallerThan->RealClusters)) continue;
if (Here->RealClusters == ClusterEnd - ClusterStart) {
sprintf(s1,"Returning perfect fit: %I64d[%lu]",Here->Lcn,
Here->RealClusters);
ShowDebugMessage(4,Here,s1);
return(Here);
}
if (Here->RealClusters > ClusterEnd - ClusterStart) continue;
if ((BestFit != NULL) &&
(Here->RealClusters < BestFit->RealClusters)) continue;
BestFit = Here;
}
/* If no file was found then return NULL. */
if (BestFit == NULL) {
ShowDebugMessage(4,NULL," No files fit the gap.");
return(NULL);
}
/* If PerfectFit is NO, or the total size of all the files after the gap is
less than the size of the gap (perfect fit is impossible), then return
the largest file. */
if ((PerfectFit == NO) ||
(TotalSize < ClusterEnd - ClusterStart)) {
sprintf(s1,"Returning best fit: %I64d[%lu]",BestFit->Lcn,BestFit->RealClusters);
ShowDebugMessage(4,BestFit,s1);
return(BestFit);
}
/* Call myself to test if the remainder of the free block can be completely
filled with other files. If so then return the file. */
sprintf(s1,"Partial fit: %I64d[%lu]",BestFit->Lcn,BestFit->RealClusters);
ShowDebugMessage(4,BestFit,s1);
if (Depth >= 100) {
ShowDebugMessage(4,NULL," Out of depth.");
return(NULL);
}
_ftime64(&Time);
if (Time.time * 1000 + Time.millitm > MaxTime) {
ShowDebugMessage(4,NULL," Out of time.");
return(NULL);
}
Here = FindBestFile(ClusterStart + BestFit->RealClusters,ClusterEnd,PerfectFit,
BestFit,MaxTime,Depth+1);
if (Here != NULL) {
sprintf(s1,"Returning perfect fit: %I64d[%lu]",BestFit->Lcn,BestFit->RealClusters);
ShowDebugMessage(4,BestFit,s1);
return(BestFit);
}
/* Try fitting again with smaller files. */
sprintf(s1," Trying files smaller than %lu",BestFit->RealClusters);
ShowDebugMessage(4,NULL,s1);
SmallerThan = BestFit;
}
/* Should never get here. */
return(NULL);
}
/* Return the number of fragments in the file and the size of the
file in clusters. If the file could not be opened then return
zero fragments. Very small files are stored by Windows in the
directory and the number of clusters will then be zero. */
int AnalyzeFile(
char *FileName,
int Directory,
DWORD *Fragments,
DWORD *RealClusters,
DWORD *UncompressedClusters,
ULONG64 *Lcn,
COLORREF ColorFragmented,
COLORREF ColorUnfragmented) {
HANDLE FileHandle;
STARTING_VCN_INPUT_BUFFER InBuffer;
struct {
DWORD ExtentCount;
ULONG64 StartingVcn;
struct {
ULONG64 NextVcn;
ULONG64 Lcn;
} Extents[100];
} ExtentData;
ULONG64 PreviousVcn;
ULONG64 PreviousLcn;
int Result;
int SystemFile;
char s1[BUFSIZ];
DWORD d;
DWORD w;
if (SystemFile == NO) {
sprintf(s1,"Analyzing: %s",FileName);
ShowDebugMessage(3,NULL,s1);
}
/* Open the item as a file or as a directory. If the item could
not be opened then exit. */
if (Directory == NO) {
FileHandle = CreateFile(FileName,FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,OPEN_EXISTING,FILE_FLAG_NO_BUFFERING,NULL);
} else {
FileHandle = CreateFile(FileName,GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
}
if (FileHandle == INVALID_HANDLE_VALUE) return(NO);
/* Get the clustermap of this file. The loop will repeat if there
are more datablocks than fit in the Data. */
InBuffer.StartingVcn.QuadPart = 0;
PreviousLcn = MAXULONG64;
PreviousVcn = 0;
do {
Result = DeviceIoControl(FileHandle,FSCTL_GET_RETRIEVAL_POINTERS,
&InBuffer,sizeof(InBuffer),
&ExtentData,sizeof(ExtentData),
&w,NULL);
if (Result == 0) {
Result = GetLastError();
if (Result != ERROR_MORE_DATA) break;
}
/* Count the number of fragments, and calculate the total number of
clusters used by this file. */
if (PreviousVcn == 0) PreviousVcn = ExtentData.StartingVcn;
for (d = 0; d < ExtentData.ExtentCount; d++) {
sprintf(s1," Extent %u, Lcn=%I64u, Vcn=%I64u, NextVcn=%I64u",
d+1,ExtentData.Extents[d].Lcn,PreviousVcn,ExtentData.Extents[d].NextVcn);
if (SystemFile == NO) ShowDebugMessage(3,NULL,s1);
/* Next datablock. */
InBuffer.StartingVcn.QuadPart = PreviousVcn;
} while (Result == ERROR_MORE_DATA);
/* If the file is not fragmented then draw in green. */
if (*Fragments <= 1) {
DrawCluster(*Lcn,*Lcn + *RealClusters,ColorUnfragmented);
}
CloseHandle(FileHandle);
return(YES);
}
/* Look for a block of empty clusters on disk. If the MinimumLcn is
not zero then the block must be at or above it, if the MinimumSize is
not zero then the block must have at least that many contiguous
free clusters. Return YES if succes, NO if no block was found or
an error occurred.
The routine will reload the entire cluster bitmap from Windows every
time. It would be faster to load the clustermap into memory and use
it throughout the run of the program, but that would cause more
fails because of stale information. */
int FindFreeBlock(
HANDLE VolumeHandle,
ULONG64 MinimumLcn, /* Cluster must be at or above this LCN. */
DWORD MinimumSize, /* Cluster must be at least this big. */
ULONG64 *BeginLcn, /* Result, LCN of begin of cluster. */
ULONG64 *EndLcn) { /* Result, LCN of end of cluster. */
STARTING_LCN_INPUT_BUFFER InBuffer;
struct {
ULONG64 StartingLcn;
ULONG64 BitmapSize;
BYTE Buffer[32768]; /* Most efficient if binary multiple. */
} Data;
ULONG64 Lcn;
ULONG64 ClusterStart;
int Index;
int IndexMax;
BYTE Mask;
int InUse;
int PrevInUse;
int Result;
char s1[BUFSIZ];
char s2[BUFSIZ];
DWORD w;
/* Main loop to walk through the entire clustermap. */
Lcn = MinimumLcn;
ClusterStart = 0;
PrevInUse = 1;
do {
/* Fetch a block of cluster data. */
InBuffer.StartingLcn.QuadPart = Lcn;
Result = DeviceIoControl(VolumeHandle,FSCTL_GET_VOLUME_BITMAP,
&InBuffer,sizeof(InBuffer),
&Data,sizeof(Data),
&w,NULL);
if (Result == 0) {
Result = GetLastError();
if (Result != ERROR_MORE_DATA) {
SystemErrorStr(GetLastError(),s1);
sprintf(s2,"ERROR: could not get volume bitmap: %s",s1);
ShowDebugMessage(1,NULL,s2);
return(NO);
}
}
/* Analyze the clusterdata. We resume where the previous block left
off. If a cluster is found that matches the criteria then return
it's LCN (Logical Cluster Number). */
Lcn = Data.StartingLcn;
Index = 0;
Mask = 1;
IndexMax = sizeof(Data.Buffer);
if (Data.BitmapSize / 8 < IndexMax) IndexMax = (int)(Data.BitmapSize / 8);
while (Index < IndexMax) {
InUse = (Data.Buffer[Index] & Mask);
if (((Lcn >= Excludes[0].Start) && (Lcn < Excludes[0].End)) ||
((Lcn >= Excludes[1].Start) && (Lcn < Excludes[1].End)) ||
((Lcn >= Excludes[2].Start) && (Lcn < Excludes[2].End))) {
InUse = 1;
}
if ((PrevInUse == 0) && (InUse != 0)) {
sprintf(s1,"Free cluster found: LCN=%I64d, Size=%I64d",
ClusterStart,Lcn - ClusterStart);
ShowDebugMessage(3,NULL,s1);
/* Move a file to a new location on disk. The new location must be a
contiguous block of free clusters. Moving the file will automatically
defragment it.
Return codes:
0 Success.
1 Cannot move file because new location is inside the file.
2 Cannot move file because it has no clusters.
3 The file could not be opened, permission denied.
4 Error in Windows defragmenter API.
5 Windows has fragmented the file.
WARNING: the file will be moved to a different location in the FileList
because it gets a new LCN. */
int MoveToLcn(HANDLE VolumeHandle, struct FileListStruct *File, ULONG64 NewLcn,
int Strategy) {
MOVE_FILE_DATA MoveParams;
struct {
DWORD ExtentCount;
ULONG64 StartingVcn;
struct {
ULONG64 NextVcn;
ULONG64 Lcn;
} Extents[1000];
} ExtentData;
ULONG64 PreviousVcn;
int Result;
int SystemFile;
DWORD ErrorCode;
DWORD Fragments;
DWORD w;
char s1[BUFSIZ];
char s2[BUFSIZ];
DWORD d;
/* If the new location is inside the file then we cannot move it. */
if ((NewLcn >= File->Lcn) && (NewLcn < File->Lcn + File->RealClusters)) {
return(1);
}
/* If the file has no size then we cannot move it. */
if (File->RealClusters == 0) return(2);
SystemFile = IsSystemFile(File->FileName);
if (SystemFile == NO) {
if (File->RealClusters == 1) {
sprintf(s1,"Moving 1 cluster from %I64d to %I64d.",File->Lcn,NewLcn);
} else {
sprintf(s1,"Moving %ld clusters from %I64d to %I64d.",
File->RealClusters,File->Lcn,NewLcn);
}
ShowProgressMessage(s1,File->FileName);
}
/* Open the item as a file or as a directory. If the item could
not be opened then exit. */
if (File->Directory == NO) {
MoveParams.FileHandle = CreateFile(File->FileName,FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,OPEN_EXISTING,FILE_FLAG_NO_BUFFERING,NULL);
} else {
MoveParams.FileHandle = CreateFile(File->FileName,GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
}
if (MoveParams.FileHandle == INVALID_HANDLE_VALUE) {
if (SystemFile == YES) return(3);
SystemErrorStr(GetLastError(),s1);
sprintf(s2,"Could not open: %s",s1);
ShowDebugMessage(1,File,s2);
return(3);
}
/* Walk through the clustermap of the item and move all segments to the
new location. If the clusters at the old location are contiguous then
move as a single block. */
PreviousVcn = 0;
MoveParams.StartingLcn.QuadPart = NewLcn;
MoveParams.StartingVcn.QuadPart = 0;
MoveParams.ClusterCount = 0;
ErrorCode = ERROR_MORE_DATA;
while (ErrorCode == ERROR_MORE_DATA) {
ErrorCode = NO_ERROR;
Result = DeviceIoControl(MoveParams.FileHandle,
FSCTL_GET_RETRIEVAL_POINTERS,&PreviousVcn,sizeof(PreviousVcn),
&ExtentData,sizeof(ExtentData),&w,NULL);
if (Result == 0) {
ErrorCode = GetLastError();
if (ErrorCode != ERROR_MORE_DATA) break;
}
for (d = 0; d < ExtentData.ExtentCount; d++) {
if (SystemFile == NO) {
sprintf(s1," Extent %u, Lcn=%I64u, Vcn=%I64u, NextVcn=%I64u",
d+1,ExtentData.Extents[d].Lcn,PreviousVcn,ExtentData.Extents[d].NextVcn);
ShowDebugMessage(3,NULL,s1);
}
if (ExtentData.Extents[d].Lcn != MAXULONG64) {
if (Strategy == 0) {
MoveParams.ClusterCount = MoveParams.ClusterCount +
(DWORD)(ExtentData.Extents[d].NextVcn - PreviousVcn);
DrawCluster(ExtentData.Extents[d].Lcn,
ExtentData.Extents[d].Lcn + ExtentData.Extents[d].NextVcn - PreviousVcn,
ColorEmpty);
} else {
MoveParams.StartingVcn.QuadPart = PreviousVcn;
MoveParams.ClusterCount = (DWORD)(ExtentData.Extents[d].NextVcn - PreviousVcn);
DrawCluster(ExtentData.Extents[d].Lcn,
ExtentData.Extents[d].Lcn + MoveParams.ClusterCount,
ColorBusy);
DrawCluster(MoveParams.StartingLcn.QuadPart,
MoveParams.StartingLcn.QuadPart + MoveParams.ClusterCount,
ColorBusy);
Result = DeviceIoControl(VolumeHandle,FSCTL_MOVE_FILE,&MoveParams,
sizeof(MoveParams),NULL,0,&w,NULL);
DrawCluster(ExtentData.Extents[d].Lcn,
ExtentData.Extents[d].Lcn + MoveParams.ClusterCount,
ColorEmpty);
DrawCluster(MoveParams.StartingLcn.QuadPart,
MoveParams.StartingLcn.QuadPart + MoveParams.ClusterCount,
ColorUnfragmented);
if (Result == 0) {
ErrorCode = GetLastError();
break;
}
MoveParams.StartingLcn.QuadPart = MoveParams.StartingLcn.QuadPart +
MoveParams.ClusterCount;
}
}
PreviousVcn = ExtentData.Extents[d].NextVcn;
}
}
if ((Strategy == 0) && (ErrorCode == NO_ERROR) && (MoveParams.ClusterCount > 0)) {
DrawCluster(NewLcn,NewLcn + MoveParams.ClusterCount,ColorBusy);
Result = DeviceIoControl(VolumeHandle,FSCTL_MOVE_FILE,&MoveParams,
sizeof(MoveParams),NULL,0,&w,NULL);
DrawCluster(NewLcn,NewLcn + MoveParams.ClusterCount,ColorEmpty);
if (Result == 0) ErrorCode = GetLastError();
}
FlushFileBuffers(MoveParams.FileHandle); /* Is this useful? Can't hurt. */
CloseHandle(MoveParams.FileHandle);
/* Change the LCN of the file in it's record in the FileList.
The record needs to be deleted and then re-inserted into the
list. */
TreeDetach(File,&FileList);
AnalyzeFile(File->FileName,File->Directory,&Fragments,&File->RealClusters,
&File->UncompressedClusters,&File->Lcn,ColorFragmented,ColorUnfragmented);
/* Note: if the file is now fragmented then it would be logical to
skip the following line, not adding the record back into the
list. That would cause a memory leak though. We cannot delete
the record because it is still used by whatever routine called
us. */
TreeInsert(File,&FileList);
/* If FSCTL_GET_RETRIEVAL_POINTERS or FSCTL_MOVE_FILE reported an error
then show the error message and return 4. These errors are very rare
and only happen in very special circustances, such as the volume
beiing dismounted or something like that. */
if ((ErrorCode != ERROR_MORE_DATA) && (ErrorCode != NO_ERROR)) {
SystemErrorStr(ErrorCode,s1);
if (SystemFile == NO) ShowDebugMessage(1,File,s1);
return(4);
}
/* If Windows has fragmented the file by moving it then return 5. */
if (Fragments > 1) return(5);
return(0);
}
/* Move a file to a new location on disk. The new location must be a
contiguous block of free clusters. Moving the file will automatically
defragment it.
Return YES if the file was succesfully moved to the requested location
and is not fragmented. Return NO in all other cases.
*/
int MoveTheFile(HANDLE VolumeHandle, struct FileListStruct *File, ULONG64 NewLcn) {
ULONG64 OldLcn;
ULONG64 ClusterStart;
ULONG64 ClusterEnd;
WIN32_FILE_ATTRIBUTE_DATA Attributes;
FILETIME SystemTime1;
ULARGE_INTEGER FileTime;
ULARGE_INTEGER SystemTime;
int Result;
ULONG64 n1;
/* Move the file to the requested LCN. */
OldLcn = File->Lcn;
Result = MoveToLcn(VolumeHandle,File,NewLcn,0);
if (Result == 0) return(YES); /* Success. */
if ((Result == 1) || /* Location is inside file. */
(Result == 2) || /* File has no clusters. */
(Result == 3) || /* Could not open file. */
(Result == 4)) { /* Error in Windows API. */
DeleteFromFileList(File);
return(NO);
}
/* The file was fragmented by the Windows defragmentation API. This usually
happens when (part of) the gap was previously in use by another file but
has not yet been released by the NTFS checkpoint system.
The FSCTL_GET_VOLUME_BITMAP call then returns the space as empty, but it
cannot be used, a severe shortcoming of the API. */
/* If the file has changed in the last 15 minutes then it's probably actively
used, such as a database. Chances are we won't be able to defragment it,
so leave it fragmented. */
Result = GetFileAttributesEx(File->FileName,GetFileExInfoStandard,&Attributes);
if (Result != 0) {
FileTime.LowPart = Attributes.ftLastWriteTime.dwLowDateTime;
FileTime.HighPart = Attributes.ftLastWriteTime.dwHighDateTime;
GetSystemTimeAsFileTime(&SystemTime1);
SystemTime.LowPart = SystemTime1.dwLowDateTime;
SystemTime.HighPart = SystemTime1.dwHighDateTime;
n1 = 10000000;
n1 = n1 * 15 * 60;
if (FileTime.QuadPart + n1 > SystemTime.QuadPart) return(NO);
}
/* Move the file away to another place on disk. */
Result = FindFreeBlock(VolumeHandle,OldLcn + File->RealClusters,
File->RealClusters,&ClusterStart,&ClusterEnd);
if (Result == NO) return(NO);
Result = MoveToLcn(VolumeHandle,File,ClusterStart,0);
/* If this resulted yet again in a fragmented file then try once more.
Sigh... */
if (Result == 5) {
Result = FindFreeBlock(VolumeHandle,ClusterStart + File->RealClusters,
File->RealClusters,&ClusterStart,&ClusterEnd);
if (Result == NO) return(NO);
Result = MoveToLcn(VolumeHandle,File,ClusterStart,0);
}
return(NO);
}
/* Scan a directory and all it's subdirectories (recursive) for
fragmented files, and defragment them. The function also creates
a list of files and their locations in memory for later use by
the optimizer. */
void ScanDir(
HANDLE VolumeHandle,
char *Path, /* No trailing slash */
DWORD *TotalFileCount,
DWORD *TotalDirCount,
DWORD *DeFragmentedFileCount,
ULONG64 *DeFragmentedByteCount,
DWORD *FragmentedFilesCount) {
HANDLE FindHandle;
WIN32_FIND_DATA FindFileData;
char SearchPath[BUFSIZ];
int Directory;
char FileName[BUFSIZ];
ULONG64 Bytes;
DWORD Fragments;
DWORD RealClusters;
DWORD UncompressedClusters;
ULONG64 Lcn;
ULONG64 ClusterStart;
ULONG64 ClusterEnd;
struct FileListStruct *File;
int Result;
char s1[BUFSIZ];
char s2[BUFSIZ];
sprintf(SearchPath,"%s\\*",Path);
FindHandle = FindFirstFile(SearchPath,&FindFileData);
if (FindHandle == INVALID_HANDLE_VALUE) return;
do {
if (Running != RUNNING) break;
if (strcmp(FindFileData.cFileName,".") == 0) continue;
if (strcmp(FindFileData.cFileName,"..") == 0) continue;
/* Ignore reparse-points, a directory where a volume is mounted
with the MOUNTVOL command. */
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
continue;
}
/* Analyze the item: number of clusters, LCN (the physical location on
harddisk), and number of fragments. */
Directory = NO;
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) Directory = YES;
Bytes = FindFileData.nFileSizeHigh * ((ULONG64)MAXDWORD + 1) +
FindFileData.nFileSizeLow;
sprintf(FileName,"%s\\%s",Path,FindFileData.cFileName);
Result = AnalyzeFile(FileName,Directory,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorFragmented,ColorUnfragmented);
/* If unsuccesful then try again with the short filename. I don't know why,
but CreateFile() can fail on long filenames with special characters,
and succeed on the short filename. */
if ((Result == NO) && (strlen(FindFileData.cAlternateFileName) > 0)) {
sprintf(FileName,"%s\\%s",Path,FindFileData.cAlternateFileName);
Result = AnalyzeFile(FileName,Directory,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorFragmented,ColorUnfragmented);
}
if (Result == NO) {
if (IsSystemFile(FileName) == NO) {
sprintf(FileName,"%s\\%s",Path,FindFileData.cFileName);
SystemErrorStr(GetLastError(),s1);
sprintf(s2,"Could not open '%s': %s",FileName,s1);
ShowDebugMessage(2,NULL,s2);
}
continue;
}
/* Ignore the item if it has no LCN and no clusters. Very small
files are stored in the MFT and are reported by Windows as
having zero LCN and clusters. */
if ((Lcn == 0) && (RealClusters == 0)) {
/* Iterate subdirectories. */
if (Directory == YES) {
ScanDir(VolumeHandle,FileName,TotalFileCount,TotalDirCount,
DeFragmentedFileCount,DeFragmentedByteCount,FragmentedFilesCount);
}
continue;
}
/* Add the item to the list of files in memory. */
File = AddToFileList(FileName,Bytes,Lcn,RealClusters,UncompressedClusters,
Directory,FindFileData.ftLastWriteTime);
if (BalanceCount > 1000) FileList = TreeBalance(FileList);
/* If it's a directory then iterate subdirectories. */
if (Directory == YES) {
ScanDir(VolumeHandle,FileName,TotalFileCount,TotalDirCount,
DeFragmentedFileCount,DeFragmentedByteCount,FragmentedFilesCount);
}
/* Loop for unfragmented items; nothing more to do. */
if (Fragments <= 1) continue;
/* Show info about the file. */
ShowDebugMessage(4,File,"Defragmenting");
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0) {
sprintf(s1,"Special file attribute: Compressed");
ShowDebugMessage(4,File,s1);
}
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0) {
sprintf(s1,"Special file attribute: Encrypted");
ShowDebugMessage(4,File,s1);
}
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) != 0) {
sprintf(s1,"Special file attribute: Offline");
ShowDebugMessage(4,File,s1);
}
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {
sprintf(s1,"Special file attribute: Read-only");
ShowDebugMessage(4,File,s1);
}
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0) {
sprintf(s1,"Special file attribute: Sparse-file");
ShowDebugMessage(4,File,s1);
}
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) != 0) {
sprintf(s1,"Special file attribute: Temporary");
ShowDebugMessage(4,File,s1);
}
sprintf(s1,"%lu clusters at %I64d, %I64d bytes, %lu fragments",
RealClusters,Lcn,Bytes,Fragments);
ShowDebugMessage(4,File,s1);
/* Defragment the item by moving it to a new place on disk. */
Result = FindFreeBlock(VolumeHandle,0,RealClusters,&ClusterStart,&ClusterEnd);
if (Result == NO) {
ShowDebugMessage(3,File,"File is bigger than largest free block, cannot defragment.");
*FragmentedFilesCount = *FragmentedFilesCount + 1;
continue;
}
Result = MoveToLcn(VolumeHandle,File,ClusterStart,0);
if (Result == 0) { /* Success. */
*DeFragmentedFileCount = *DeFragmentedFileCount + 1;
*DeFragmentedByteCount = *DeFragmentedByteCount + Bytes;
}
if ((Result == 1) || /* Location is inside file. */
(Result == 2) || /* File has no clusters. */
(Result == 3) || /* Could not open file. */
(Result == 4) || /* Error in Windows API. */
(Result == 5)) { /* File is now fragmented. */
/* Note: If the file is now fragmented then it is almost certainly a file
that cannot be defragmented. Some running programs will lock part of a
file, such as MySQL, or create files with special extents, such as Norton
Ghost. */
DeleteFromFileList(File);
*FragmentedFilesCount = *FragmentedFilesCount + 1;
}
} while (FindNextFile(FindHandle,&FindFileData) != 0);
FindClose(FindHandle);
}
/* Defragment all the files in a volume. */
void DefragmentVolume(HANDLE VolumeHandle, char *VolumeDescription) {
DWORD TotalFileCount;
DWORD TotalDirCount;
DWORD DeFragmentedFileCount;
ULONG64 DeFragmentedByteCount;
DWORD FragmentedFilesCount;
char s1[BUFSIZ];
/* Optimize directories by moving them all to the beginning of the disk,
before any files. */
void OptimizeDirectories(HANDLE VolumeHandle) {
struct FileListStruct *Item;
struct FileListStruct *TempItem;
struct FileListStruct *BestFit;
int Result;
ULONG64 ClusterStart;
ULONG64 ClusterEnd;
DWORD Clusters;
/* For all items on disk. */
DirectoriesTop = 0;
for (Item = TreeSmallest(FileList); Item != NULL; Item = TreeNext(Item)) {
if (Running != RUNNING) return;
/* If the item is a directory then loop. */
if (Item->Directory == YES) {
DirectoriesTop = Item->Lcn + Item->RealClusters;
continue;
}
/* Try to fill all the free blocks before the Item with directories. */
ClusterStart = 0;
while (1) {
if (Running != RUNNING) return;
/* Find the next free block. If it's after the Item then exit the loop. */
Result = FindFreeBlock(VolumeHandle,ClusterStart,0,&ClusterStart,&ClusterEnd);
if (Result == NO) return;
if (ClusterStart >= Item->Lcn) break;
/* Try to fill the free block with the biggest possible directories from
above it. */
while (ClusterStart < ClusterEnd) {
/* Walk through all the next items and select the best fitting
directory. If no directory was found then exit the loop. */
BestFit = NULL;
for (TempItem = Item; TempItem != NULL; TempItem = TreeNext(TempItem)) {
if (TempItem->Directory == NO) continue;
if (TempItem->RealClusters > ClusterEnd - ClusterStart) continue;
if ((BestFit != NULL) &&
(TempItem->RealClusters < BestFit->RealClusters)) continue;
BestFit = TempItem;
}
if (BestFit == NULL) {
ClusterStart = ClusterEnd;
break;
}
/* Move the directory. */
Result = MoveToLcn(VolumeHandle,BestFit,ClusterStart,0);
if (Result == 0) { /* Success. */
ClusterStart = ClusterStart + BestFit->RealClusters;
}
if ((Result == 1) || /* Location is inside file. */
(Result == 2) || /* File has no clusters. */
(Result == 3) || /* Could not open file. */
(Result == 4)) { /* Error in Windows API. */
DeleteFromFileList(BestFit);
}
if (Result == 5) ClusterEnd = 0; /* File is now fragmented. */
}
}
/* If there are no directories after the Item then we are done, exit. */
Clusters = 0;
for (TempItem = TreeNext(Item); TempItem != NULL; TempItem = TreeNext(TempItem)) {
if (TempItem->Directory == YES) Clusters = Clusters + TempItem->RealClusters;
}
if (Clusters == 0) return;
/* Move the Item away. If the item could not be moved then skip to next
item. */
TempItem = TreePrev(Item);
Result = FindFreeBlock(VolumeHandle,Item->Lcn + Clusters,Item->RealClusters,
&ClusterStart,&ClusterEnd);
if (Result == NO) return;
Result = MoveToLcn(VolumeHandle,Item,ClusterStart,0);
Item = TempItem;
if ((Result == 1) || /* Location is inside file. */
(Result == 2) || /* File has no clusters. */
(Result == 3) || /* Could not open file. */
(Result == 4)) { /* Error in Windows API. */
Item = TreeNext(Item);
}
}
}
/* Make a 1% free space after the directories, and move all files that
are in the MFT reserved area. */
void OptimizeFreeSpace(HANDLE VolumeHandle) {
struct FileListStruct *File;
struct FileListStruct *TempFile;
ULONG64 ClusterStart;
ULONG64 ClusterEnd;
WIN32_FILE_ATTRIBUTE_DATA Attributes;
FILETIME SystemTime1;
ULARGE_INTEGER FileTime;
ULARGE_INTEGER SystemTime;
int Result;
ULONG64 n1;
char s1[BUFSIZ];
/* Calculate the top of the free space, is the top of the directories plus
1% of the size of the disk. */
FreeSpaceTop = DirectoriesTop + MaxLcn / 100;
/* Write a debug message. */
sprintf(s1,"DirectoriesTop=%I64d, FreeSpaceTop=%I64d",DirectoriesTop,FreeSpaceTop);
ShowDebugMessage(3,NULL,s1);
/* Move all the files that are inside the MFT reserved zones to normal
diskspace. Windows reserves a percentage of the disk for the MFT. When
the disk becomes full the reserved space may be used by normal files.
This section looks for files in those reserved spaces and moves them
to normal diskspace. */
for (File = TreeSmallest(FileList); File != NULL; File = TreeNext(File)) {
if (Running != RUNNING) break;
/* Move the file. */
Result = FindFreeBlock(VolumeHandle,FreeSpaceTop,File->RealClusters,
&ClusterStart,&ClusterEnd);
if (Result == NO) {
sprintf(s1,"Cannot move file away because no gap is big enough: %I64d[%lu]",
File->Lcn,File->RealClusters);
ShowDebugMessage(1,File,s1);
continue;
}
TempFile = TreePrev(File);
Result = MoveTheFile(VolumeHandle,File,ClusterStart);
File = TempFile;
if (Result == NO) File = TreeNext(File);
}
/* Create a free space between the directories and the files. This gives
Windows a chance to place temporary files at the beginning of the
harddisk, which is nice and fast.
- Free space just after the directories
- 1% of total disk space
- Move files older than 1 day
*/
GetSystemTimeAsFileTime(&SystemTime1);
SystemTime.LowPart = SystemTime1.dwLowDateTime;
SystemTime.HighPart = SystemTime1.dwHighDateTime;
n1 = 10000000;
n1 = n1 * 24 * 60 * 60;
for (File = TreeSmallest(FileList); File != NULL; File = TreeNext(File)) {
if (Running != RUNNING) break;
if (File->Lcn >= FreeSpaceTop) break;
if (File->Directory == YES) continue;
/* Leave files that are less than 1 day old. */
Result = GetFileAttributesEx(File->FileName,GetFileExInfoStandard,&Attributes);
if (Result == 0) continue;
FileTime.LowPart = Attributes.ftLastWriteTime.dwLowDateTime;
FileTime.HighPart = Attributes.ftLastWriteTime.dwHighDateTime;
if (FileTime.QuadPart + n1 > SystemTime.QuadPart) continue;
/* Move the file. */
Result = FindFreeBlock(VolumeHandle,FreeSpaceTop,File->RealClusters,
&ClusterStart,&ClusterEnd);
if (Result == NO) {
sprintf(s1,"Cannot move file away because no gap is big enough: %I64d[%lu]",
File->Lcn,File->RealClusters);
ShowDebugMessage(1,File,s1);
continue;
}
TempFile = TreePrev(File);
Result = MoveTheFile(VolumeHandle,File,ClusterStart);
File = TempFile;
if (Result == NO) File = TreeNext(File);
}
}
/* Optimize the harddisk by filling holes with files from above. Return
YES if something was changed, NO if nothing happened. */
void OptimizeVolume(HANDLE VolumeHandle) {
struct FileListStruct *File;
ULONG64 ClusterStart;
ULONG64 ClusterEnd;
ULONG64 AwayClusterStart;
ULONG64 AwayClusterEnd;
int Result;
int Retry;
int PerfectFit;
char s1[BUFSIZ];
/* Walk through all the gaps. */
ClusterStart = FreeSpaceTop;
Retry = 0;
while (Running == RUNNING) {
if (BalanceCount > 1000) FileList = TreeBalance(FileList);
/* Find the next gap. */
Result = FindFreeBlock(VolumeHandle,ClusterStart,0,&ClusterStart,&ClusterEnd);
if (Result == NO) break;
/* Loop until the gap is filled. First look for combinations of files that
perfectly fit the gap, otherwise fill with the largest fitting files. */
PerfectFit = YES;
while ((ClusterStart < ClusterEnd) && (Retry < 5) && (Running == RUNNING)) {
/* Find the file that is the best fit for the gap and move it. If success
then loop. */
File = FindBestFile(ClusterStart,ClusterEnd,PerfectFit,NULL,0,0);
if (File != NULL) {
Result = MoveTheFile(VolumeHandle,File,ClusterStart);
if (Result == YES) {
ClusterStart = ClusterStart + File->RealClusters;
Retry = 0;
} else {
ClusterEnd = ClusterStart; /* Force re-scan of gap. */
Retry = Retry + 1;
}
continue;
}
/* Try again without PerfectFit. */
if (PerfectFit == NO) break;
PerfectFit = NO;
/* If not in full-optimize mode then loop. */
if (DoOptimize != 2) continue;
/* Move the file just above the free block away. */
File = FindFileAtLcn(ClusterEnd);
if (File == NULL) {
sprintf(s1,"Don't know which file is at the end of the gap: %I64d[%I64d]",
ClusterStart,ClusterEnd - ClusterStart);
ShowDebugMessage(1,NULL,s1);
continue;
}
Result = FindFreeBlock(VolumeHandle,File->Lcn,File->RealClusters,
&AwayClusterStart,&AwayClusterEnd);
if (Result == NO) {
sprintf(s1,"Cannot move file away because no gap is big enough: %I64d[%lu]",
File->Lcn,File->RealClusters);
ShowDebugMessage(1,File,s1);
continue;
}
sprintf(s1,"Enlarging gap %I64d[%I64d] by moving %I64d[%lu]",ClusterStart,
ClusterEnd - ClusterStart,File->Lcn,File->RealClusters);
ShowDebugMessage(1,NULL,s1);
Result = MoveTheFile(VolumeHandle,File,AwayClusterStart);
if (Result == YES) {
ClusterEnd = ClusterEnd + File->RealClusters;
PerfectFit = YES;
} else {
ClusterEnd = ClusterStart;
}
}
/* If the free block could not be filled then skip. */
if (ClusterStart < ClusterEnd) {
sprintf(s1,"Skipping gap, cannot fill: %I64d[%I64d]",ClusterStart,ClusterEnd - ClusterStart);
ShowDebugMessage(1,NULL,s1);
ClusterStart = ClusterEnd;
Retry = 0;
}
}
}
if (Excludes[0].Start > 0) DrawCluster(Excludes[0].Start,Excludes[0].End,ColorUnmovable);
if (Excludes[1].Start > 0) DrawCluster(Excludes[1].Start,Excludes[1].End,ColorUnmovable);
if (Excludes[2].Start > 0) DrawCluster(Excludes[2].Start,Excludes[2].End,ColorUnmovable);
Result = AnalyzeFile("c:\\$Mft",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$MftMirr",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$LogFile",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$Volume",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$AttrDef",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$Bitmap",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$Boot",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$BadClus",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$Secure",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$Upcase",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
Result = AnalyzeFile("c:\\$Extend",NO,&Fragments,&RealClusters,
&UncompressedClusters,&Lcn,ColorUnmovable,ColorUnmovable);
}
/* Open the VolumeHandle. If error then leave. */
VolumeHandle = CreateFile(VolumeName,GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
if (VolumeHandle == INVALID_HANDLE_VALUE) {
SystemErrorStr(GetLastError(),s1);
sprintf(s2,"Error while opening volume \"%s\": %s",VolumeDescription,s1);
ShowDebugMessage(1,NULL,s2);
return;
}
/* If the volume is not mounted then leave. Unmounted volumes can be
defragmented, but the system administrator probably has unmounted
the volume because he wants it untouched. */
if (DeviceIoControl(VolumeHandle,FSCTL_IS_VOLUME_MOUNTED,NULL,0,NULL,0,&w,NULL) == 0) {
sprintf(s1,"Skipping volume '%s' because it is not mounted.",VolumeDescription);
ShowGlobalMessage(s1);
CloseHandle(VolumeHandle);
return;
}
/* Determine the maximum LCN. A single call to FSCTL_GET_VOLUME_BITMAP
is enough, we don't have to walk through the entire bitmap.
It's a pity we have to do it in this roundabout manner, because
there is no system call that reports the total number of clusters
in a volume. GetDiskFreeSpace() does, but is limited to 2Gb volumes,
GetDiskFreeSpaceEx() reports in bytes, not clusters, and
FSCTL_GET_NTFS_VOLUME_DATA only works for NTFS volumes. */
InBuffer.StartingLcn.QuadPart = MaxLcn;
Result = DeviceIoControl(VolumeHandle,FSCTL_GET_VOLUME_BITMAP,
&InBuffer,sizeof(InBuffer),
&Data,sizeof(Data),
&w,NULL);
if (Result == 0) {
Result = GetLastError();
if (Result != ERROR_MORE_DATA) {
sprintf(s1,"Cannot defragment volume: %s",VolumeDescription);
ShowDebugMessage(1,NULL,s1);
CloseHandle(VolumeHandle);
return;
}
}
MaxLcn = Data.StartingLcn + Data.BitmapSize;
/* Setup the list of clusters that cannot be used. The Master File
Table cannot be moved and cannot be used by files. All this is
only necessary for NTFS volumes. */
Result = DeviceIoControl(VolumeHandle,FSCTL_GET_NTFS_VOLUME_DATA,
NULL,0,&NtfsData,sizeof(NtfsData),&w,NULL);
if (Result != 0) {
/* Note: NtfsData.TotalClusters.QuadPart should be exactly the same
as the MaxLcn that was determined in the previous block. */
Excludes[0].Start = NtfsData.MftStartLcn.QuadPart;
Excludes[0].End = NtfsData.MftStartLcn.QuadPart +
NtfsData.MftValidDataLength.QuadPart / NtfsData.BytesPerCluster;
Excludes[1].Start = NtfsData.MftZoneStart.QuadPart;
Excludes[1].End = NtfsData.MftZoneEnd.QuadPart;
Excludes[2].Start = NtfsData.Mft2StartLcn.QuadPart;
Excludes[2].End = NtfsData.Mft2StartLcn.QuadPart +
NtfsData.MftValidDataLength.QuadPart / NtfsData.BytesPerCluster;
/* Close the volume handle. */
CloseHandle(VolumeHandle);
/* Cleanup the FileList. */
DeleteFileList(FileList);
FileList = NULL;
}
/* Defragment and Optimize all harddisk-volumes on the machine.
If a WhichDisks is specified then only defragment those volumes that
contain the mask. For example: if WhichDisks="C:" then only process
the C: disk. */
void ProcessAllVolumes(char *WhichDisks) {
DWORD Drives;
HANDLE FindVolumeHandle;
char VolumeName[BUFSIZ];
char VolumeDescription[BUFSIZ];
int DriveType;
DWORD VolumeFlags;
int Result;
char s1[BUFSIZ];
char s2[BUFSIZ];
char *p1;
int i;
Drives = GetLogicalDrives();
FindVolumeHandle = FindFirstVolume(VolumeName,BUFSIZ);
if (FindVolumeHandle == INVALID_HANDLE_VALUE) {
SystemErrorStr(GetLastError(),s1);
sprintf(s2,"Could not get list of volumes: %s",s1);
ShowGlobalMessage(s2);
return;
}
while (TRUE) {
DriveType = GetDriveType(VolumeName);
if (DriveType == DRIVE_FIXED) {
strcpy(VolumeDescription,"?");
/* If the volume has a label then use it as VolumeDescription. */
if (GetVolumeInformation(VolumeName,s1,BUFSIZ,NULL,NULL,&VolumeFlags,NULL,0) != 0) {
if (strlen(s1) != 0) strcpy(VolumeDescription,s1);
}
/* If the volume is mounted on a drive (A...Z) then use that as
VolumeDescription. */
for (i = 0; i < 26; i++) {
if ((Drives & (1 << i)) != 0) {
sprintf(s1,"%c:\\",i+'A');
if ((GetVolumeNameForVolumeMountPoint(s1,s2,BUFSIZ) != 0) &&
(stricmp(s2,VolumeName) == 0)) {
sprintf(VolumeDescription,"%c:",i+'A');
}
}
}
/* Strip the trailing backslash from the VolumeName, CreateFile() doesn't
like it. */
strcpy(s1,VolumeName);
p1 = strchr(s1,'\0');
if (p1 != s1) {
p1--;
*p1 = '\0';
}
if ((WhichDisks == NULL) ||
(*WhichDisks == '\0') ||
(stristr(s1,WhichDisks) != NULL) ||
(stristr(VolumeDescription,WhichDisks) != NULL)) {
ProcessVolume(s1,VolumeDescription);
}
}
if (FindNextVolume(FindVolumeHandle,VolumeName,BUFSIZ) == 0) {
Result = GetLastError();
if (Result == ERROR_NO_MORE_FILES) break;
SystemErrorStr(Result,s1);
sprintf(s2,"Error while walking list of volumes: %s",s1);
ShowGlobalMessage(s2);
break;
}
}
FindVolumeClose(FindVolumeHandle);
}
/* A small wrap-around subroutine to handle Ctrl-C keyboard exit. */
BOOL WINAPI CtrlCHandler(DWORD dwCtrlType) {
Running = STOPPING;
return(FALSE);
}
int main(int argc, char **argv) {
int i;
int DoAllVolumes;
/* Process commandline parameters. */
DoAllVolumes = YES;
if (argc > 1) {
for (i = 1; i < argc; i++) {
if (strcmp(argv[i],"-d") == 0) {
i++;
Debug = atol(argv[i]);
continue;
}
if (strcmp(argv[i],"-o") == 0) {
DoOptimize = NO;
continue;
}
if (strcmp(argv[i],"-o1") == 0) {
DoOptimize = 1;
continue;
}
if (strcmp(argv[i],"-o2") == 0) {
DoOptimize = 2;
continue;
}
if ((strcmp(argv[i],"-h") == 0) || (strcmp(argv[i],"/?") == 0)) {
fprintf(stdout,USAGE,*argv);
continue;
}
ProcessAllVolumes(argv[i]);
DoAllVolumes = NO;
}
}
/* If the program was started in a console then setup a Ctrl-C
handler. This ensures that the driver will be unloaded when the
user terminates the program. */
if (isatty(fileno(stdin)) != 0) SetConsoleCtrlHandler(CtrlCHandler,TRUE);
вопрос первый:
FSCTL_GET_VOLUME_BITMAP
The FSCTL_GET_VOLUME_BITMAP control code retrieves a bitmap of occupied and available clusters on a volume.
To perform this operation, call the DeviceIoControl function with the following parameters.
BOOL DeviceIoControl(
(HANDLE) hDevice, // handle to volume
FSCTL_GET_VOLUME_BITMAP, // dwIoControlCode
(LPVOID) lpInBuffer, // input buffer
(DWORD) nInBufferSize, // size of input buffer
(LPVOID) lpOutBuffer, // output buffer
(DWORD) nOutBufferSize, // size of output buffer
(LPDWORD) lpBytesReturned, // number of bytes returned
(LPOVERLAPPED) lpOverlapped // OVERLAPPED structure
);
Parameters
hDevice
[in] Handle to the volume from which the bitmap is to be retrieved. To retrieve a device handle, call the CreateFile function. Only administrators can open volume handles.
Encrypted files must be opened with FILE_READ_DATA, FILE_WRITE_DATA, FILE_APPEND_DATA, or FILE_EXECUTE access. Other files need only be opened with FILE_READ_ATTRIBUTES access. For more information, see File and Directory Security.
dwIoControlCode
[in] Control code for the operation. This value identifies the specific operation to be performed and the type of device on which to perform it. Use FSCTL_GET_VOLUME_BITMAP for this operation.
lpInBuffer
[in] Pointer to the input buffer, a STARTING_LCN_INPUT_BUFFER structure. The starting LCN in the input buffer may be rounded down by the operation.
nInBufferSize
[in] Size, in bytes, of the input buffer.
lpOutBuffer
[out] Pointer to the output buffer, a VOLUME_BITMAP_BUFFER variably sized structure.
nOutBufferSize
[in] Size, in bytes, of the output buffer.
lpBytesReturned
[out] Pointer to a DWORD variable that receives the size, in bytes, of the data stored in the buffer pointed to by lpOutBuffer.
If the output buffer is too small to return any data, then the call fails, GetLastError returns the error code ERROR_INSUFFICIENT_BUFFER, and the returned byte count is zero.
If the output buffer is too small to hold all of the data but can hold some entries, then the operating system returns as much as fits, the call fails, GetLastError returns the error code ERROR_MORE_DATA, and lpBytesReturned indicates the amount of data returned. Your application should call DeviceIoControl again with the same operation, specifying a new starting point.
If lpOverlapped is NULL (nonoverlapped I/O), lpBytesReturned cannot be NULL.
If lpOverlapped is not NULL (overlapped I/O), lpBytesReturned can be NULL. If this is an overlapped operation, you can retrieve the number of bytes returned by calling the GetOverlappedResult function. If hDevice is associated with an I/O completion port, you can get the number of bytes returned by calling the GetQueuedCompletionStatus function.
lpOverlapped
[in] Pointer to an OVERLAPPED structure.
If hDevice was opened with the FILE_FLAG_OVERLAPPED flag, lpOverlapped must point to a valid OVERLAPPED structure. In this case, DeviceIoControl is performed as an overlapped (asynchronous) operation. If the device was opened with the FILE_FLAG_OVERLAPPED flag and lpOverlapped is NULL, the function fails in unpredictable ways.
If hDevice was opened without specifying the FILE_FLAG_OVERLAPPED flag, lpOverlapped is ignored and DeviceIoControl does not return until the operation has been completed, or until an error occurs.
Return Values
If the operation succeeds, DeviceIoControl returns a nonzero value.
If the operation fails, DeviceIoControl returns zero. To get extended error information, call GetLastError.
A return value from GetLastError of ERROR_MORE_DATA indicates to the caller that the buffer was not large enough to accommodate a complete bitmap from the requested starting LCN to the last cluster on the volume. In this event, you can make subsequent FSCTL_GET_VOLUME_BITMAP calls to retrieve the remaining bitmap.
A return value from GetLastError of ERROR_INVALID_PARAMETER indicates either that there is no input buffer, that the starting LCN requested in the input buffer was less than zero, or that the starting LCN was greater than or equal to the total clusters on the volume.
A return value from GetLastError of ERROR_NOT_READY indicates that the volume is an NTFS volume, and it is not mounted.
Remarks
The FSCTL_GET_VOLUME_BITMAP control code retrieves a data structure that describes the allocation state of each cluster in the file system from the requested starting LCN to the last cluster on the volume. The bitmap uses one bit to represent each cluster:
The value 1 indicates that the cluster is allocated (in use).
The value 0 indicates that the cluster is not allocated (free).
Note that the bitmap represents a point in time, and can be incorrect as soon as it has been read if the volume has write activity. Thus, it is possible to attempt to move a cluster onto an allocated cluster in spite of a recent bitmap indicating that the cluster is unallocated. Programs using the DeviceIoControl FSCTL_MOVE_FILE control code must be prepared for this possibility.
The handle used here must be a Volume handle and have been opened with any access. Note that only Administrators can open Volume handles.
The starting LCN in the input buffer may be rounded down before the bitmap is calculated. The rounding limit is file system dependent.
For the implications of overlapped I/O on this operation, see the Remarks section of the DeviceIoControl topic.
This operation aligns the bitmap it returns on a page boundary.
Windows XP: This operation aligns the bitmap it returns on a byte boundary.
Requirements
Windows NT/2000/XP: Included in Windows 2000 and later.
Windows 95/98/Me: Unsupported.
Header: Declared in Winioctl.h.
See Also
Defragmentation, File System Control Codes, DeviceIoControl, CreateFile, GetLastError, GetOverlappedResult, GetQueuedCompletionStatus, STARTING_LCN_INPUT_BUFFER, VOLUME_BITMAP_BUFFER, FSCTL_MOVE_FILE, OVERLAPPED
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.