Table des matières

Surveiller modification Fichier/Répertoire

Pistes de recherches

FindFirstChangeNotification

Cette méthode est documentée ici → Obtention de notifications de modification de répertoire

Apparemment cette méthode permet de savoir si un dossier a été modifier, mais pas de connaitre les modification apportées. Quel fichier modifié ? Quel fichier ajouté ou supprimé ?

Comment ça marche ?

Pour faire simple :

On récupere les Handle des répértoires a surveiller avec FindFirstChangeNotification

function FindFirstChangeNotification(lpPathName:LPCSTR; bWatchSubtree:WINBOOL; dwNotifyFilter:DWORD):HANDLE;

On met ces Handle dans un array de ce type : (par exemple)

THandleList = array [0..MAXIMUM_WAIT_OBJECTS - 1] of THandle;

Ensuite on attends une modification avec WaitForMultipleObjects:

function WaitForMultipleObjects( nCount:DWORD; lpHandles : PWOHandleArray; bWaitAll:WINBOOL; dwMilliseconds:DWORD):DWORD

La fonction WaitForMultipleObjects retourne un DWord qui peut être interprété de la façon suivante:

Plus d'infos → Valeur de retour de la fonction WaitForMultipleObjects

On traite la valeur retournée par la fonction WaitForMultipleObjects.
Puis on retourne a la surveillance par un FindNextChangeNotification et rebouclage sur WaitForMultipleObjects:

function FindNextChangeNotification(hChangeHandle:HANDLE):WINBOOL;

ou le parametre hChangeHandle est le Handle du répertoire modifié precedement.
Ce Handle est récuperable dans la liste des Handles THandleList passé a la fonction WaitForMultipleObjects a l'index retourné par cette méme fonction… (suis-je bien claire ???)

Exemple de code (Dans un Thread, car WaitForMultipleObjects est une fonction bloquante…) :

   // vIndex: integer;
   // vHandleList : THandleList (voir ci-dessus)
   // FCount = nombre de handle dans la liste vHandleList
   // FWaitMs = nombre de millisecondes a attendre
   // FDirChangedHandle = Handle du Find Change Notifier du répertoire qui a été modifié
   // Le Handle du Find Change Notifier est retourné par la function FindFirstChangeNotification (voir ci-dessus)
         while not Terminated do
           begin
            vIndex := WaitForMultipleObjects(FCount, vHandleList[0], False, FWaitMs);
            if (vIndex >= WAIT_OBJECT_0) and
               (vIndex < WAIT_OBJECT_0 + FNHandleList.Count)  then
              begin
                  FDirChangedHandle :=FHandleList[vIndex - WAIT_OBJECT_0];
                  ...
                  FindNextChangeNotification( FDirChangedHandle);
              end;
           end;

ReadDirectoryChangesW

Exemple de code

Voici un exemple de code pour l'utilisation de ReadDirectoryChangesW:

Documentation utile pour mieux comprendre ce code :

DirMonDOS.lpr
program DirMonDOS;
 
{$mode objfpc}{$H+}
 
uses
 {$IFDEF UNIX}
 {$IFDEF UseCThreads}
   cthreads,
 {$ENDIF}
 {$ENDIF}
   Classes,
   crt,
   JwaWindows,
   SysUtils;
 
var
   FPathName: string;
   FDirHandle: THandle;
   FBuffer:  array[0..9999] of byte;
   FBytesReturned: dword;
   FOverLapped: TOverlapped;
   FOverResult: boolean;
   FFileNotifyInformation: PFileNotifyInformation;
   FOffset:  dword;
   FAction:  dword;
   FActionStr: string;
   FFilename: string;
   FLen:     dword;
   FTerminate: boolean;
   FLastError: dword;
   FNotifyFilter: DWord;
   FLooping: boolean;
 
begin
   { Répertoire à surveiller }
   FPathName     := 'J:\';
 
   { Initialisation des variables }
   FTerminate := False;
   FLooping   := False;
   ZeroMemory(@FBuffer, sizeof(FBuffer));
   FNotifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or
      FILE_NOTIFY_CHANGE_LAST_WRITE;
   { FILE_NOTIFY_CHANGE_FILE_NAME = 1;
     FILE_NOTIFY_CHANGE_DIR_NAME = 2;
     FILE_NOTIFY_CHANGE_ATTRIBUTES = 4;
     FILE_NOTIFY_CHANGE_SIZE = 8;
     FILE_NOTIFY_CHANGE_LAST_WRITE = 16;
     FILE_NOTIFY_CHANGE_LAST_ACCESS = 32;
     FILE_NOTIFY_CHANGE_CREATION = 64;
     FILE_NOTIFY_CHANGE_SECURITY = 256; }
 
   writeln('Press [s] to exit');
   WriteLn('Surveillance du dossier [', FPathName, ']');
 
   { Récuperation du Handle du Répertoire a surveiller }
   FDirHandle := CreateFile(PChar(FPathName), FILE_LIST_DIRECTORY,
      FILE_SHARE_READ or FILE_SHARE_DELETE or FILE_SHARE_WRITE, nil,
      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
   WriteLn('Handle du dossier [', FDirHandle, ']');
 
 
   if FDirHandle <> INVALID_HANDLE_VALUE then
     begin
        try
          { Bouclage jusqu'a ce que Terminate=True }
          repeat
             { Mise en place de la surveillance }
             if ReadDirectoryChangesW(FDirHandle, @FBuffer, sizeOf(FBuffer),
               True, FNotifyFilter, @FBytesReturned, @FOverLapped, nil) then
              begin
 
               { Bouclage jusqu'a ce que FOverResult=True }
               repeat
                  { Si FOverResult = True, cela signifie qu'il y a eu des modifications }
                  FOverResult :=
                     GetOverlappedResult(FDirHandle, FOverLapped,
                     FBytesReturned, False);
                  { Je capture GetLastError ci-dessous, car il peut etre modifié
                    par d'autres instructions, comme un WriteLn par exemple }
                  FLastError  := GetLastError;
 
                  { Affichage des informations pour faire beau }
                  if not FOverResult and not FLooping then
                    begin
                     Writeln('Looping...');
                     FLooping := True;
                    end;
                  sleep(100);
                  { Verification si on a pressé la touche [S] pour arreter le processus }
                  if KeyPressed and (ReadKey = 's') then
                     FTerminate := True;
 
               until FOverResult or (FLastError <> ERROR_IO_INCOMPLETE) or FTerminate;
               FLooping := False;
 
               if FOverResult then
                 begin
                  Writeln('--- begin modification ---');
 
                  { Positionnement sur le debut du Buffer }
                  pointer(FFileNotifyInformation) := @FBuffer[0];
                  repeat
                     FOffset := FFileNotifyInformation^.NextEntryOffset;
                     FAction := FFileNotifyInformation^.Action;
                     { FLen = FileNameLength/2
                       car FileName est codé en WideString
                       et WideString est 2x plus long que AnsiString
                       ->https://wiki.freepascal.org/Character_and_string_types/fr }
                     FLen    := FFileNotifyInformation^.FileNameLength div 2;
                     WideCharLenToStrVar(@(FFileNotifyInformation^.FileName),
                        FLen, FFilename);
 
                     case FAction of
                        FILE_ACTION_ADDED: FActionStr    := 'Created';
                        FILE_ACTION_REMOVED: FActionStr  := 'Deleted';
                        FILE_ACTION_MODIFIED: FActionStr := 'Modified';
                        FILE_ACTION_RENAMED_OLD_NAME: FActionStr := 'RenamedFrom';
                        FILE_ACTION_RENAMED_NEW_NAME: FActionStr := 'RenamedTo';
                        else
                           FActionStr := '???';
                       end;
 
                     { Affichage du résultat }
                     WriteLn('Action [', FAction, '|', FActionStr, '] len[',
                        FLen, '] sur fichier [', FFilename, ']');
 
                     { Positionnement sur le prochain FFileNotifyInformation }
                     pointer(FFileNotifyInformation) :=
                        pointer(FFileNotifyInformation) + FOffset;
 
                  until FOffset = 0;
                  { Nettoyage du Buffer pour eviter d'accumuler des parasites
                    surtout dans Filename }
                  ZeroMemory(@FBuffer, sizeof(FBuffer));
                  Writeln('--- end modification ---');
                 end;
              end;
         until FTerminate;
        finally
         CloseHandle(FDirHandle);
        end;
     end
   else
      Writeln('Dir Handle invalid');
   Writeln('Press to exit');
   Readln;
 
end.

Ressources pour ReadDirectoryChangesW

A la Procmon

Procmon est un excellent utilitaire qui montre en temps réel les accès aux fichiers par les processus.
Reste a savoir comment il fait ?

Apparemment cette solution impliquerai l'utilisation d'un drivers annexe, ce qui reste relativement fastidieux…

Piste OpenFilesView de Nirsoft

Une piste peut etre sur la page→https://www.nirsoft.net/utils/opened_files_view.html.
Un programme de Nirsoft apparement similaire a Procmon qui fonctionne de la facon suivante:

OpenedFilesView utilise l'API NtQuerySystemInformation pour énumérer tous les handles dans le système.
Après avoir filtré les handles non-fichiers, il utilise un driver temporaire - NirSoftOpenedFilesDriver.sys pour lire les informations sur chaque handle depuis la mémoire du noyau.
Ce driver est automatiquement déchargé du système lorsque vous quittez l'utilitaire OpenedFilesView.

Projet de ShareVB

Sur cette page : Savoir quel fichier est utilisé par telle application : la liste des fichiers ouverts sur le système (pour 2k/xp/2k3) ShareVB partage un projet en VB qui ferait apparemment le taf… a étudier.

Je met ce projet sur mon site, on sait jamais dés fois qu'il disparaisse !!! 36281-1110458-savoir-quel-fichier-est-utilise-par-telle-application-la-liste-des-fichiers-ouverts-sur-le-systeme-pour-2k-xp-2k3.zip

En vrac à étudier

Sources & Ressources