ReplaceFile Alternative, wenn die Anwendung die Datei gesperrt hält

Editor FooEdit (nennen wir es) verwendetDatei ersetze () beim Speichern, um sicherzustellen, dass der Speichervorgang effektiv atomar ist und dass die Originaldatei auf der Disc erhalten bleibt, wenn etwas schief geht. (Der andere wichtige Vorteil von ReplaceFile () ist die Kontinuität der Dateiidentität - Erstellungsdatum und andere Metadaten.)

FooEdit behält auch ein Handle für die Datei mit einem Freigabemodus von nur FILE_SHARE_READ offen, sodass andere Prozesse die Datei öffnen können, aber nicht darauf schreiben können, während FooEdit sie zum Schreiben geöffnet hat.

"Offensichtlich" muss dieses Handle kurz geschlossen werden, während der ReplaceFile-Vorgang ausgeführt wird. Dies ermöglicht ein Rennen, in dem ein anderer Prozess die Datei möglicherweise mit Schreibzugriff öffnen kann, bevor FooEdit das FILE_SHARE_READ-Sperrhandle wiederherstellt.

(Wenn FooEdit das FILE_SHARE_READ-Handle nicht schließt, bevor ReplaceFile () aufgerufen wird, schlägt ReplaceFile () mit einer Verletzung der Freigabe fehl.)

Ich würde gerne wissen, was der einfachste Weg ist, um dieses Rennen zu lösen. Die Optionen scheinen zu sein, entweder eine andere Möglichkeit zu finden, die mit ReplaceFile () kompatible Datei zu sperren (ich verstehe nicht, wie dies möglich ist) oder das gesamte Verhalten von ReplaceFile () zu replizieren, aber ein vorhandenes Dateihandle zu verwenden Greifen Sie auf die Zieldatei und nicht auf einen Pfad zu. Ich bin ein bisschen festgefahren, wie alle Operationen von ReplaceFile () atomar über den Benutzercode ausgeführt werden können (und die Neuimplementierung von ReplaceFile () scheint sowieso eine schlechte Idee zu sein).

Dies muss ein häufiges Problem sein, daher gibt es wahrscheinlich eine offensichtliche Lösung, die ich verpasst habe.

(Diese Frage scheint verwandt zu sein, hat aber keine Antwort:Transaktionales Schreiben einer Dateiänderung unter Windows.)

Hier ist ein minimal überprüfbares Beispiel, das zeigt, was ich erreichen möchte (aktualisiert am 13:18 30/9/2015 UTC). Sie müssen drei Dateinamen als Befehlszeilenargumente angeben, die sich alle auf demselben Volume befinden. Das erste muss schon existieren.

Ich erhalte immer eine Freigabeverletzung von ReplaceFile ().

#include <Windows.h>
#include <stdio.h>
#include <assert.h>
int main(int argc, char *argv[])
{
  HANDLE lock;
  HANDLE temp;
  DWORD  bytes;

  if (argc != 4)
  {
    puts("First argument is the project file. Second argument is the temporary file.");
    puts("The third argument is the backup file.");
  }

  /* Open and lock the project file to make sure no one else can modify it */
  lock = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, 0);
  assert(lock != INVALID_HANDLE_VALUE);

  /* Save to the temporary file. */
  temp = CreateFile(argv[2], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, 0, 0);
  assert(temp != INVALID_HANDLE_VALUE);
  WriteFile(temp, "test", 4, &bytes, NULL);
  /* Keep temp open so that another process can't modify the file. */

  if (!ReplaceFile(argv[1], argv[2], argv[3], 0, NULL, NULL))
  {
    if (GetLastError() == ERROR_SHARING_VIOLATION)
      puts("Sharing violation as I expected");
    else
      puts("Something went wrong");
  }
  else
    puts("ReplaceFile worked - not what I expected");

  /* If it worked the file referenced by temp would now be called argv[1]. */
  CloseHandle(lock);
  lock = temp;

  return EXIT_SUCCESS;
}

Danke an Hans Passant, der in einer nun gestrichenen Antwort einige wertvolle klärende Gedanken lieferte. Folgendes habe ich entdeckt, als ich seinen Vorschlägen nachgegangen bin:

Es scheint, ReplaceFile () erlaubt lpReplacedFileName um offen zu sein FILE_SHARE_READ | FILE_SHARE_DELETE, aber lpReplacementFileName kann nicht sein. (Und dieses Verhalten scheint nicht davon abzuhängen, ob lpBackupFileName wird mitgeliefert.) Es ist also durchaus möglich, eine Datei zu ersetzen, die ein anderer Prozess geöffnet hat, auch wenn dieser andere Prozess FILE_SHARE_WRITE nicht zulässt, was Hans 'Argument war.

Aber FooEdit versucht sicherzustellen, dass kein anderer Prozess die Datei mit GENERIC_WRITE öffnen kannan erster Stell. Um in FooEdit sicherzustellen, dass es kein Rennen gibt, in dem ein anderer Prozess die Ersatzdatei mit GENERIC_WRITE öffnen kann, muss FooEdit anscheinendständi eines FILE_SHARE_READ | FILE_SHARE_DELETE Handle auf lpReplacementFileName, was dann die Verwendung von ReplaceFile () ausschließt.

Antworten auf die Frage(4)

Ihre Antwort auf die Frage