Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Круглый стол
  
Правила КС
>> Настройки

Фильтр вопросов
>> Новые вопросы
отслеживать по
>> Новые ответы

Избранное

Страница вопросов
Поиск по КС


Специальные проекты:
>> К л ю к в а
>> Г о л о в о л о м к и

Вопрос №

Задать вопрос
Off-topic вопросы

Помощь

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  23:09[Войти] | [Зарегистрироваться]
Ответ на вопрос № 42989

03-06-2006 22:15
Здравствуйте, братья программисты! Писал я программу, и возникли у меня два вопроса.
Вопрос первый: Как. Сделать так, чтобы программа выполняла функцию, наподобие функции "анализа" на фрагментацию диска в  программе дефрагментации.
Вопрос второй: Как определить скорость обращения винды к файлам.
Заранее говорю вам огромное СПАСИБО!

[+] Добавить в избранные вопросы

Отслеживать ответы на этот вопрос по RSS

Ответы:


Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице.
Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.

09-06-2006 06:06
По вопросу фрагментации. Я решал такую проблему
Программа дефрагментации - defrag.exe (в ХР точно, в 98 - не проверял)
Использование:
defrag <том> [-a] [-f] [-v] [-?]
том  Буква диска, или точка подключения (например, d: или d:\vol\mpoint)
-a  Только анализ
-f  Дефрагментация даже при ограниченном месте на диске
-v  Подробные результаты
-?  Вывод справки
Нужно запускать отдельным процессом с параметрами -а и -v. Для этого использовал компонент JcCreateProcess из JVCL, из него можно вытянуть результат выполнения программы. Получаешь и обрабатываешь :)
Правда, результат выдается в кодировке DOS, но найти модуль перекодирования не проблема. Ну и соответственно время работы определения степени фрагментации будет зависеть от размера дисков - нужно подождать
З.Ы. Гасите на время работы касперского и прочую лабуду - фиг дождетесь окончания работы процесса (у меня терпения не хватало)

06-06-2006 01:27
а вот железно работающий пример на дельфи, показывает занятые/свободные кластеры, *-занятый кластер, .-пустой

program volume_bitmap;

{$APPTYPE CONSOLE}

uses
  JwaWinBase, JwaWinNt, JwaWinType;

type

    STARTING_LCN_INPUT_BUFFER = packed record
                                  StartingLcn: LARGE_INTEGER;
                                end;



    VOLUME_BITMAP_BUFFER = packed record
                              StartingLcn,
                              BitmapSize: LARGE_INTEGER;
                              Buffer: array[0..0] of byte;
                            end;

    PVOLUME_BITMAP_BUFFER = ^VOLUME_BITMAP_BUFFER;

const FILE_DEVICE_FILE_SYSTEM = $00000009;
      METHOD_NEITHER = 3;
      FILE_ANY_ACCESS = 0;

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.


05-06-2006 08:46
Если вы читаете Си, то могу дать сорц дефрагментатора

/*

  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.



  AUTHOR

      Jeroen C. Kessels
      Internet Engineer
      http://www.kessels.com/

  Copyright:  Jeroen C. Kessels
  Date:        28 may 2006
  Version:    2.19

  With thanks to Michael Adams for his help on X64.


*/




#define _WIN32_WINNT 0x0500              /* FindFirstFile */
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <io.h>                          /* isatty()*/
#include <time.h>
#include <sys/timeb.h>


#define COPYRIGHT "defrag v2.19, (c) 2006 J.C. Kessels"

#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

#define RUNNING  0
#define STOPPING 1
#define STOPPED  2
int Running = RUNNING;

int Debug;
int DoOptimize;

/* List in memory of all the files on disk, sorted by LCN (Logical
  Cluster Number). */
struct FileListStruct {
  struct FileListStruct *Parent;                      /* Parent item. */
  struct FileListStruct *Smaller;                /* Next smaller item. */
  struct FileListStruct *Bigger;                  /* Next bigger item. */
  char *FileName;
  ULONG64 Bytes;
  ULONG64 Lcn;
  DWORD RealClusters;
  DWORD UncompressedClusters;
  char Directory;
  char Unmovable;
  ULARGE_INTEGER Time;
  } *FileList = NULL;
int BalanceCount;

/* List of clusters that cannot be moved. */
ULONG64 MaxLcn;                /* Highest possible LCN + 1. */
struct {
  ULONG64 Start;
  ULONG64 End;
  } Excludes[3];

ULONG64 DirectoriesTop;        /* LCN just after the directories. */
ULONG64 FreeSpaceTop;          /* LCN just after the freespace. */







/***********************************************************************/




/* Some glue stuff, used by the Windows version. */
#define COLORREF int
COLORREF ColorEmpty;
COLORREF ColorAllocated;
COLORREF ColorUnfragmented;
COLORREF ColorUnmovable;
COLORREF ColorFragmented;
COLORREF ColorBusy;





void ShowGlobalMessage(char *Message) {
  fprintf(stdout,"%s\n",Message);
  }

void ShowProgressMessage(char *Message1, char *Message2) {
  if ((Message2 != NULL) && (*Message2 != '\0')) {
      fprintf(stdout,"%s\n",Message2);
      fprintf(stdout,"  %s\n",Message1);
    } else {
      fprintf(stdout,"%s\n",Message1);
      }
  }

void ShowDebugMessage(int Level, struct FileListStruct *File, char *Message) {
  if (Debug < Level) return;
  if (File != NULL) {
      fprintf(stdout,"%s\n",File->FileName);
      fprintf(stdout,"  %s\n",Message);
    } else {
      fprintf(stdout,"%s\n",Message);
      }
  }




/* Paint a cluster on the screen in the color. */
void DrawCluster(
    ULONG64 ClusterStart,
    ULONG64 ClusterEnd,
    COLORREF Color) {
  }




void ShowDiskmap(HANDLE VolumeHandle, char *Message) {
  ShowGlobalMessage(Message);
  }





/***********************************************************************/




/* Search case-insensitive for a substring. */
char *stristr(char *Haystack, char *Needle) {
  register char *p1;
  register int i;

  if ((Haystack == NULL) || (Needle == NULL)) return(NULL);

  p1 = Haystack;
  i = (int)strlen(Needle);
  while (*p1 != '\0') {
    if (strnicmp(p1,Needle,i) == 0) return(p1);
    p1++;
    }

  return(NULL);
  }




/* Return a string with the error message for GetLastError(). */
void SystemErrorStr(DWORD ErrorCode, char *Out) {
  char s1[BUFSIZ];

  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
    NULL,ErrorCode,0,s1,BUFSIZ,NULL);
  sprintf(Out,"error %lu: %s",ErrorCode,s1);
  }




/* 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;
        }
    }

  /* Insert the record. */
  New->Parent = Ins;
  New->Smaller = NULL;
  New->Bigger = NULL;
  if (Ins == NULL) {
      *Top = New;
    } else {
      if (Found > 0) {
          Ins->Smaller = New;
        } else {
          Ins->Bigger = New;
          }
      }

  BalanceCount = BalanceCount + 1;
  }




/* 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);
  }




/* Detach (unlink) a record from the tree. The record is not freed().
  See: http://www.stanford.edu/~blp/avl/libavl.html/Deleting-from-a-BST.html
  */
void TreeDetach(struct FileListStruct *Item, struct FileListStruct **Top) {
  struct FileListStruct *B;

  /* Sanity check. */
  if ((Top == NULL) || (*Top == NULL) || (Item == NULL)) return;

  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;

  File = FileList;
  while (File != NULL) {
    if (File->Lcn == Lcn) return(File);
    if (Lcn < File->Lcn) {
        File = File->Smaller;
      } else {
        File = File->Bigger;
        }
    }
  return(NULL);
  }





/* 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 (MaxTime == 0) {
    _ftime64(&Time);
    MaxTime = Time.time * 1000 + Time.millitm + 500;
    }

  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;

  *Fragments = 0;
  *RealClusters = 0;
  *UncompressedClusters = 0;
  *Lcn = 0;
  SystemFile = IsSystemFile(FileName);

  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);

      if (PreviousLcn == MAXULONG64) *Lcn = ExtentData.Extents[d].Lcn;
      if (ExtentData.Extents[d].Lcn != MAXULONG64) {
        if (ExtentData.Extents[d].Lcn != PreviousLcn) *Fragments = *Fragments + 1;
        PreviousLcn = ExtentData.Extents[d].Lcn +
          (DWORD)(ExtentData.Extents[d].NextVcn - PreviousVcn);
        *RealClusters = *RealClusters +
          (DWORD)(ExtentData.Extents[d].NextVcn - PreviousVcn);
        DrawCluster(ExtentData.Extents[d].Lcn,ExtentData.Extents[d].Lcn +
          (DWORD)(ExtentData.Extents[d].NextVcn - PreviousVcn),ColorFragmented);
        }
      *UncompressedClusters = *UncompressedClusters +
        (DWORD)(ExtentData.Extents[d].NextVcn - PreviousVcn);
      PreviousVcn = ExtentData.Extents[d].NextVcn;
      }

    /* 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 {

    /* Sanity check. */
    if ((MaxLcn > 0) && (Lcn >= MaxLcn)) return(NO);

    /* 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);

        if ((ClusterStart >= MinimumLcn) &&
            (Lcn - ClusterStart >= MinimumSize)) {
          *BeginLcn = ClusterStart;
          if (EndLcn != NULL) *EndLcn = Lcn;
          return(YES);
          }
        }
      if ((PrevInUse != 0) && (InUse == 0)) ClusterStart = Lcn;
      PrevInUse = InUse;
      if (Mask == 128) {
          Mask = 1;
          Index = Index + 1;
        } else {
          Mask = Mask << 1;
          }
      Lcn = Lcn + 1;
      }

    } while ((Result == ERROR_MORE_DATA) &&
            (Lcn < Data.StartingLcn + Data.BitmapSize));

  if (PrevInUse == 0) {
    sprintf(s1,"Free cluster found: LCN=%I64d, Size=%I64d",
      ClusterStart,Lcn - ClusterStart);
    ShowDebugMessage(3,NULL,s1);

    if ((ClusterStart >= MinimumLcn) &&
        (Lcn - ClusterStart >= MinimumSize)) {
      *BeginLcn = ClusterStart;
      if (EndLcn != NULL) *EndLcn = Lcn;
      return(YES);
      }
    }

  return(NO);
  }





/* 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];

  *TotalDirCount = *TotalDirCount + 1;
  ShowDebugMessage(2,NULL,Path);

  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;
      }

    /* Show progress counter. */
    *TotalFileCount = *TotalFileCount + 1;
#ifdef WINDEFRAG
    sprintf(s1,"Analyzing file: %lu",*TotalFileCount);
    ShowProgressMessage(s1,Path);
#else
    if (*TotalFileCount % 1000 == 0) {
      sprintf(s1,"Analyzing file: %lu",*TotalFileCount);
      ShowProgressMessage(s1,"");
      }
#endif

    /* 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];

  TotalFileCount = 0;
  TotalDirCount = 0;
  DeFragmentedFileCount = 0;
  DeFragmentedByteCount = 0;
  FragmentedFilesCount = 0;
  ScanDir(VolumeHandle,VolumeDescription,&TotalFileCount,&TotalDirCount,
    &DeFragmentedFileCount,&DeFragmentedByteCount,&FragmentedFilesCount);

  sprintf(s1,"%lu files",TotalFileCount);
  ShowDebugMessage(3,NULL,s1);
  sprintf(s1,"%lu directories",TotalDirCount);
  ShowDebugMessage(3,NULL,s1);
  sprintf(s1,"%lu files defragmented, %I64d bytes",DeFragmentedFileCount,DeFragmentedByteCount);
  ShowDebugMessage(3,NULL,s1);
  sprintf(s1,"%lu files still fragmented",FragmentedFilesCount);
  ShowDebugMessage(3,NULL,s1);
  }




/* 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;

    /* Ignore files outside the reserved zones. */
    if ((File->Lcn < Excludes[0].Start) || (File->Lcn >= Excludes[0].End) &&
        (File->Lcn < Excludes[1].Start) || (File->Lcn >= Excludes[1].End) &&
        (File->Lcn < Excludes[2].Start) || (File->Lcn >= Excludes[2].End)) 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);
    }

  /* 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;
      }
    }
  }




void ShowSystemFiles() {
  DWORD Fragments;
  DWORD RealClusters;
  DWORD UncompressedClusters;
  ULONG64 Lcn;
  int Result;

  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);
  }




/* Defragment and Optimize a volume (harddisk). Examples:
    ProcessVolume("\\\\.\\C:","C:");
    ProcessVolume("\\\\?\\Volume{0d08a1e2-7649-11d9-bf72-806d6172696f}","C:");
  */
void ProcessVolume(char *VolumeName, char *VolumeDescription) {
  HANDLE VolumeHandle;
  time_t Now;
  STARTING_LCN_INPUT_BUFFER InBuffer;
  struct {
    ULONG64 StartingLcn;
    ULONG64 BitmapSize;
    BYTE Buffer[8];
    } Data;
  int Result;
  NTFS_VOLUME_DATA_BUFFER NtfsData;
  char s1[BUFSIZ];
  char s2[BUFSIZ];
  DWORD w;

  /* Initialize. */
  MaxLcn = 0;
  Excludes[0].Start = 0;
  Excludes[0].End = 0;
  Excludes[1].Start = 0;
  Excludes[1].End = 0;
  Excludes[2].Start = 0;
  Excludes[2].End = 0;
  DeleteFileList(FileList);
  FileList = NULL;
  BalanceCount = 0;

  /* Initialize random number generator. */
  time(&Now);
  srand((unsigned int)Now);

  sprintf(s1,"Opening volume '%s' ('%s')",VolumeDescription,VolumeName);
  ShowDebugMessage(5,NULL,s1);

  /* 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;

    /* Show debug info. */
    sprintf(s1,"MftStartLcn=%I64d, MftZoneStart=%I64d, MftZoneEnd=%I64d, Mft2StartLcn=%I64d, MftValidDataLength=%I64d",
      NtfsData.MftStartLcn.QuadPart,NtfsData.MftZoneStart.QuadPart,
      NtfsData.MftZoneEnd.QuadPart,NtfsData.Mft2StartLcn.QuadPart,
      NtfsData.MftValidDataLength.QuadPart / NtfsData.BytesPerCluster);
    ShowDebugMessage(4,NULL,s1);
    sprintf(s1,"Excludes[0].Start=%I64d, Excludes[0].End=%I64d",
      Excludes[0].Start,Excludes[0].End);
    ShowDebugMessage(4,NULL,s1);
    sprintf(s1,"Excludes[1].Start=%I64d, Excludes[1].End=%I64d",
      Excludes[1].Start,Excludes[1].End);
    ShowDebugMessage(4,NULL,s1);
    sprintf(s1,"Excludes[2].Start=%I64d, Excludes[2].End=%I64d",
      Excludes[2].Start,Excludes[2].End);
    ShowDebugMessage(4,NULL,s1);
    }

  /* Defragment and optimize. */
  sprintf(s1,"Defragmenting volume %s",VolumeDescription);
  ShowDiskmap(VolumeHandle,s1);
  ShowSystemFiles();
  if (Running == RUNNING) {
    DefragmentVolume(VolumeHandle,VolumeDescription);
    }
  if ((Running == RUNNING) && (DoOptimize != 0)) {
    ShowGlobalMessage("Optimizing directories");
    OptimizeDirectories(VolumeHandle);
    }
  if ((Running == RUNNING) && (DoOptimize != 0)) {
    ShowGlobalMessage("Optimizing free space");
    OptimizeFreeSpace(VolumeHandle);
    }
  if ((Running == RUNNING) && (DoOptimize != 0)) {
    sprintf(s1,"Optimizing disk %s",VolumeDescription);
    ShowGlobalMessage(s1);
    OptimizeVolume(VolumeHandle);
    }
  ShowGlobalMessage("Finished.");
  ShowProgressMessage("","");

  /* 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;

  fprintf(stdout,"%s\n",COPYRIGHT);

  /* Setup the defaults. */
  Debug = 1;
  DoOptimize = 1;

  /* 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);

  if (DoAllVolumes == YES) ProcessAllVolumes(NULL);

/*
  ProcessAllVolumes("C:");
  ProcessAllVolumes("C:\\t2");
  ProcessAllVolumes("b7599f02-b963-11d9-9abc-00047598d110");
*/

  return(0);
  }

04-06-2006 02:09 | Сообщение от автора вопроса
Спасибо большое за ответ, однако я боюсь, что у меня вряд-ли получится разобраться во всём этом.Может кто-нибудь попожет?

04-06-2006 00:48
вопрос первый:
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


Добавьте свое cообщение

Вашe имя:  [Войти]
Ваш адрес (e-mail):На Королевстве все адреса защищаются от спам-роботов
контрольный вопрос:
Однажды, в студеную зимнюю пору я из лесу вышел, был сильный ЧТО?
в качестве ответа на вопрос или загадку следует давать только одно слово в именительном падеже и именно в такой форме, как оно используется в оригинале.
Надоело отвечать на странные вопросы? Зарегистрируйтесь на сайте.
Тип сообщения:
Текст:
Жирный шрифт  Наклонный шрифт  Подчеркнутый шрифт  Выравнивание по центру  Список  Заголовок  Разделительная линия  Код  Маленький шрифт  Крупный шрифт  Цитирование блока текста  Строчное цитирование
  • вопрос Круглого стола № XXX

  • вопрос № YYY в тесте № XXX Рыцарской Квинтаны

  • сообщение № YYY в теме № XXX Базарной площади
  • обсуждение темы № YYY Базарной площади
  •  
     Правила оформления сообщений на Королевстве

    Страница избранных вопросов Круглого стола.
      
    Время на сайте: GMT минус 5 часов

    Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
    Функция может не работать в некоторых версиях броузеров.

    Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
    Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

     
    © При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
    Все используемые на сайте торговые марки являются собственностью их производителей.

    Яндекс цитирования