C++ / Win32: comment attendre une suppression en attente pour terminer?

Résolu:
* Solution réalisable: @ sbi
* Explication de ce qui se passe réellement: @ Hans
* Explication de la raison pour laquelle OpenFile ne passe pas par "DELETE PENDING": @ Benjamin

Le Problème:
Notre logiciel est en grande partie un moteur d'interpréteur pour un langage de script propriétaire. Ce langage de script a la capacité de créer un fichier, de le traiter, puis de le supprimer. Ce sont toutes des opérations distinctes, et aucun handle de fichier n'est maintenu ouvert dans entre ces opérations. (c'est-à-dire pendant le fichier Créer un handle est créé, utilisé pour l'écriture, puis fermé. Pendant la partie de traitement de fichier, un handle de fichier séparé ouvre le fichier, lit à partir de celui-ci et est fermé à EOF. et enfin, delete utilise :: DeleteFile qui n'utilise qu'un nom de fichier, pas un handle de fichier du tout).

Récemment, nous avons réalisé qu'une macro particulière (script) ne parvient parfois pas à créer le fichier à un moment ultérieur aléatoire (c'est-à-dire qu'elle réussit pendant les cent premières itérations de "créer, traiter, supprimer", mais quand il revient à la créer une Cent première fois, Windows répond "Accès refusé").

En regardant plus profondément dans le problème, j'ai écrit un programme très simple qui boucle sur quelque chose comme ceci:

while (true) {
  HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == INVALID_HANDLE_VALUE)
      return OpenFailed;
  const DWORD dwWrite = strlen(pszFilename);
  DWORD dwWritten;
  if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
      return WriteFailed;
  if (!CloseHandle(hFile))
      return CloseFailed;
  if (!DeleteFileA(pszFilename))
      return DeleteFailed;
}

Comme vous pouvez le voir, c'est direct à L'API Win32, et sacrément simple. Je crée un fichier, y écris, ferme la poignée, le supprime, rince, répète...

Mais quelque part le long de la ligne, je vais obtenir un Access Denied (5) Erreur lors de L'appel CreateFile (). En regardant ProcessMonitor de sysinternal, je peux voir que le problème sous-jacent est qu'il y a une suppression en attente sur le fichier pendant que j'essaie de le créer à nouveau.

Questions:
* Existe-t-il un moyen d'attendre la fin de la suppression?
* Est-il un moyen de détecter qu'un fichier est en attente de suppression?

Nous avons essayé la première option, simplement en WaitForSingleObject() sur le HFILE. Mais le HFILE est toujours fermé avant le WaitForSingleObject s'exécute, et donc WaitForSingleObject renvoie toujours WAIT_FAILED. De toute évidence, essayer d'attendre la poignée fermée ne fonctionne pas.

Je pourrais attendre une notification de modification pour le dossier dans lequel le fichier existe. Cependant, cela semble être un kludge extrêmement lourd à ce qui est un problème seulement occasionnellement (à savoir: dans mes tests sur mon PC Win7 x64 E6600, il échoue généralement à l'itération 12000 + - sur d'autres machines, cela peut arriver dès que l'itération 7 ou 15 ou 56 ou jamais).

J'ai été incapable de discerner les arguments CreateFile() qui permettraient explicitement cet éther. Peu importe les arguments de CreateFile, il n'est vraiment pas acceptable d'ouvrir un fichier pour n'importe quel accès lorsque le fichier est en attente de suppression. Et puisque je peux voir ce comportement à la fois sur une boîte XP et sur une boîte x64 Win7, je suis tout à fait certain que c'est le comportement NTFS de base "comme prévu" par Microsoft. J'ai donc besoin d'une solution qui permet au système d'exploitation de terminer la suppression avant que je essayez de continuer, de préférence sans attacher inutilement les cycles du processeur, et sans la surcharge extrême de regarder le dossier dans lequel se trouve ce fichier (si possible).

Merci d'avoir pris le temps de lire ceci et de poster une réponse. Clarifier les Questions bienvenue!

[1] oui, cette boucle renvoie sur un échec d'écriture ou un échec de fermeture qui fuit, mais comme il s'agit d'une simple application de test de console, l'application elle-même se ferme et Windows garantit que tous les handles sont fermés par le système d'exploitation lorsqu'un la fin du processus. Donc, aucune fuite n'existe ici.

bool DeleteFileNowA(const char * pszFilename)
{
    // determine the path in which to store the temp filename
    char szPath[MAX_PATH];
    strcpy(szPath, pszFilename);
    PathRemoveFileSpecA(szPath);

    // generate a guaranteed to be unique temporary filename to house the pending delete
    char szTempName[MAX_PATH];
    if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
        return false;

    // move the real file to the dummy filename
    if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
        return false;

    // queue the deletion (the OS will delete it when all handles (ours or other processes) close)
    if (!DeleteFileA(szTempName))
        return false;

    return true;
}
31
demandé sur rmtheis 2010-09-22 00:10:03

11 réponses

Pourquoi ne pas d'abord renommer le fichier à supprimer, puis de le supprimer?

Utiliser GetTempFileName() pour obtenir un nom unique, puis utilisez MoveFile() pour renommer le fichier. Puis supprimez le fichier renommé. Si la suppression réelle est en effet asynchrone et peut entrer en conflit avec la création du même fichier (comme vos tests semblent l'indiquer), cela devrait résoudre le problème.

modifier: Bien sûr, si votre analyse est correcte et que les opérations de fichiers sont quelque peu asynchrones, cela peut introduisez le problème que vous essayez de supprimer le fichier avant le changement de nom est fait. Mais alors vous pouvez toujours continuer à essayer de supprimer dans un thread d'arrière-plan.

Modifier #2: si Hans a raison (et je suis enclin à croire son analyse), le déplacement pourrait ne pas vraiment aider, car vous pourriez ne pas être en mesure de renommer un fichier ouvert par un autre processus. (Mais alors vous pourriez, Je ne sais pas cela.) Si c'est bien le cas, la seule autre façon que je peux trouver est "continue d'essayer". Vous devrez attendre quelques millisecondes et réessayer. Gardez un délai d'attente pour abandonner quand cela n'aide pas.

15
répondu sbi 2010-09-21 20:50:38

Il existe D'autres processus dans Windows qui veulent un morceau de ce fichier. L'indexeur de recherche est un candidat évident. Ou un scanner de virus. Ils ouvriront le fichier pour un partage complet, y compris FILE_SHARE_DELETE, de sorte que les autres processus ne soient pas fortement affectés par l'ouverture du fichier.

Cela fonctionne généralement bien, sauf si vous créez / écrivez / supprimez à un taux élevé. La suppression réussira mais le fichier ne peut pas disparaître du système de fichiers jusqu'à ce que le dernier handle soit fermé. Le poignée tenue par, disons, l'indexeur de recherche. Tout programme qui tente d'ouvrir ce fichier en attente de suppression sera giflé par l'erreur 5.

Il s'agit sinon d'un problème Générique sur un système d'exploitation multitâche, vous ne pouvez pas savoir quel autre processus pourrait vouloir jouer avec vos fichiers. Votre modèle d'utilisation semble inhabituel, passez en revue d'abord. Une solution de contournement serait d'attraper l'erreur, de dormir et d'essayer à nouveau. Ou déplacer le fichier dans la corbeille avec SHFileOperation ().

20
répondu Hans Passant 2018-06-01 15:30:22

Suggestion stupide-comme il échoue si rarement, que diriez-vous simplement d'attendre quelques millisecondes en cas d'échec et d'essayer à nouveau? Ou, si la latence est importante, passez à un autre nom de fichier, laissant l'ancien fichier à supprimer plus tard.

5
répondu Arkadiy 2010-09-21 20:54:40
  • Est-il un moyen de détecter qu'un fichier est en attente de suppression?

Utilisez la fonctionGetFileInformationByHandleEx avec la structureFILE_STANDARD_INFO .

Mais la fonction ne peut pas résoudre votre problème. la solution de @ sbi non plus.

4
répondu Benjamin 2010-09-23 08:19:15

Ce n'est peut-être pas votre problème particulier, mais c'est possible, donc je vous suggère de sortir Process Monitor (Sysinternals) et de voir.

J'ai eu exactement le même problème et j'ai découvert que Comodo Internet Security (cmdagent.exe) contribuait au problème. Auparavant, j'avais une machine Dual-core, mais quand j'ai mis à niveau vers un Intel i7 soudainement mon logiciel de travail (jam.exe par logiciel Perfore) ne fonctionnait plus car il avait le même modèle (une suppression puis créer, mais pas de vérification). Après le débogage du problème, j'ai trouvé que GetLastError() renvoyait l'Accès refusé, mais Process Monitor révèle un 'delete pending'. Voici la trace:

10:39:10.1738151 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Read Attributes, Delete, Disposition: Open, Options: Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
10:39:10.1738581 AM jam.exe 5032    QueryAttributeTagFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Attributes: ANCI, ReparseTag: 0x0
10:39:10.1738830 AM jam.exe 5032    SetDispositionInformationFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Delete: True
10:39:10.1739216 AM jam.exe 5032    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1739438 AM jam.exe 5032    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1744837 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1788811 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1838276 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1888407 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1936323 AM System  4   FASTIO_ACQUIRE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS SyncType: SyncTypeOther
10:39:10.1936531 AM System  4   FASTIO_RELEASE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1936647 AM System  4   IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1939064 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1945733 AM cmdagent.exe    1188    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1946532 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1947020 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1948945 AM cfp.exe 1832    QueryOpen   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   FAST IO DISALLOWED  
10:39:10.1949781 AM cfp.exe 1832    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   NAME NOT FOUND  Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
10:39:10.1989720 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0, OpenResult: Created

Comme vous pouvez le voir, il y a une demande de suppression suivie de plusieurs tentatives pour ouvrir à nouveau le fichier par jam.exe (c'est un fopen dans une boucle). Vous pouvez voir que cmdagent.exe a vraisemblablement ouvert le fichier lorsqu'il ferme son handle, puis soudainement jam.exe est capable d'ouvrir le fichier.

Bien sûr, la solution suggérée pour attendre et réessayer, et il fonctionne très bien.

4
répondu Craig 2018-09-04 15:07:36

Puisque vous créez un nouveau fichier, le traitez, puis le supprimez, il semble que vous ne vous souciez pas vraiment de sur le nom du fichier. Si c'est vraiment le cas, vous devriez envisager de toujours créer un fichier temporaire . De cette façon, à chaque fois que vous traversez le processus, vous n'avez pas à prendre soin de que le fichier n'a pas encore été supprimé.

3
répondu Jacob 2010-09-21 20:57:26

J'ai eu le même problème lors de l'utilisation de LoadLibrary(path) Je n'ai pas pu supprimer le fichier dans path.

La solution était de "fermer la poignée" ou utilisez le FreeLibrary(chemin) méthode.

Remarque: Veuillez lire les "remarques" sur MSDN concernant FreeLibrary ().

2
répondu OhadM 2015-02-13 16:35:59

Si CreateFile renvoie INVALID_HANDLE_VALUE, vous devez déterminer ce que GetLastError renvoie dans votre situation particulière (en attente de suppression) et revenir à CreateFile en fonction de ce code d'erreur uniquement.

Modifier

Le drapeau FILE_FLAG_DELETE_ON_CLOSE vous achèterait-il quelque chose?

1
répondu Pineapple 2010-09-21 21:03:06

Je suis peut-être en retard à la fête mais sur Vista/Win7 il y a DeleteFileTransacted qui supprime le fichier en utilisant des transactions qui assurent leur suppression (vidage des tampons de fichiers, etc.). Pour la compatibilité XP, ce n'est pas une option.

Une autre idée de comment cela pourrait être fait est D'OpenFile avec l'indicateur OF_CREATE qui définit la longueur à zéro si le fichier existe ou le crée si ce n'est pas le cas, puis d'appeler FlushFileBuffers sur le handle de fichier pour attendre que cette opération (rendant le fichier de longueur zéro) se termine. À la fin, le fichier est de taille 0, puis appelez simplement DeleteFile.

Vous pouvez ensuite tester si le fichier existe ou s'il a une longueur nulle pour le traiter de la même manière.

1
répondu Coder12345 2011-10-02 18:37:03

Selon [1] vous pouvez utiliser NtDeleteFile pour éviter la nature asynchrone de DeleteFile. Aussi [1] donne quelques détails sur le fonctionnement de DeleteFile.

Malheureusement, la documentation officielle sur NtDeleteFile [2] ne mentionne aucun détail particulier à ce sujet question.

[1] http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FFile%2FNtDeleteFile.html [2] https://msdn.microsoft.com/en-us/library/windows/hardware/ff566435 (v=vs. 85).aspx

1
répondu Christian K. 2017-04-17 05:56:16

Je pense que c'est juste par une mauvaise conception dans le système de fichiers. J'ai vu le même problème quand j'ai travaillé avec des ports de communication, en les ouvrant/fermant.

Malheureusement, je pense que la solution la plus simple serait de réessayer de créer le fichier plusieurs fois si vous obtenez un INVALID_HANDLE_VALUE. GetLastError() pourrait également vous donner un meilleur moyen de détecter ce INVALID_HANDLE_VALUE particulier.

J'aurais préféré les e/s superposées, mais là CloseHandle() et DeleteFile() ne gèrent pas les opérations superposées: (

-2
répondu Max Kielland 2010-09-21 20:50:24