Reemplazar alternativa de archivo cuando la aplicación mantiene el archivo bloqueado

El editor FooEdit (llamémoslo) usaReemplazar el archivo() al guardar para garantizar que la operación de guardado sea efectivamente atómica y que si algo sale mal, se conserva el archivo original en el disco. (El otro beneficio importante de ReplaceFile () es la continuidad de la identidad del archivo: fecha de creación y otros metadatos).

FooEdit también mantiene abierto un identificador para el archivo con un modo de uso compartido de solo FILE_SHARE_READ, para que otros procesos puedan abrir el archivo pero no puedan escribir mientras FooEdit lo tiene abierto para escribir.

"Obviamente", este identificador debe cerrarse brevemente mientras se lleva a cabo la operación Reemplazar archivo, y esto permite una carrera en la que otro proceso potencialmente puede abrir el archivo con acceso de escritura antes de que FooEdit restablezca su identificador de bloqueo FILE_SHARE_READ.

(Si FooEdit no cierra su identificador FILE_SHARE_READ antes de llamar a ReplaceFile (), ReemplazarFile () falla con una infracción de uso compartido).

Me gustaría saber cuál es la forma más sencilla de resolver esta carrera. Las opciones parecen ser encontrar otra forma de bloquear el archivo que es compatible con Reemplazar () (no veo cómo esto es posible) o replicar todo el comportamiento de Reemplazar (), pero usando un identificador de archivo existente para acceder al archivo de destino en lugar de una ruta. Estoy un poco atascado en cómo todas las operaciones de ReplaceFile () podrían llevarse a cabo atómicamente desde el código de usuario (y reimplementar ReplaceFile () parece una mala idea de todos modos).

Este debe ser un problema común, por lo que probablemente haya una solución obvia que me haya perdido.

(Esta pregunta parece relacionada pero no tiene respuesta:Transaccionalmente escriba un cambio de archivo en Windows.)

Aquí hay un ejemplo mínimo verificable que muestra lo que estoy tratando de lograr (actualizado 13:18 30/9/2015 UTC). Debe proporcionar tres nombres de archivo como argumentos de línea de comando, todos en el mismo volumen. El primero ya debe existir.

Siempre recibo una infracción de uso compartido de 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;
}

Gracias a Hans Passant, quien proporcionó algunas ideas aclaratorias valiosas en una respuesta ahora eliminada. Esto es lo que descubrí mientras seguía sus sugerencias:

Parece que ReplaceFile () permitelpReplacedFileName estar abierto FILE_SHARE_READ | FILE_SHARE_DELETE, perolpReplacementFileName no puede ser (Y este comportamiento no parece depender de silpBackupFileName se proporciona.) Por lo tanto, es perfectamente posible reemplazar un archivo que otro proceso ha abierto incluso si ese otro proceso no permite FILE_SHARE_WRITE, que era el punto de Hans.

Pero FooEdit está tratando de garantizar que ningún otro proceso pueda abrir el archivo con GENERIC_WRITEen primer lugar. Para garantizar en FooEdit que no hay una carrera en la que otro proceso pueda abrir el archivo de reemplazo con GENERIC_WRITE, parece que FooEdit tiene que mantenerse en esperacontinuamente de un FILE_SHARE_READ | FILE_SHARE_DELETE manejar alpReplacementFileName, que luego impide el uso de ReplaceFile ().

Respuestas a la pregunta(2)

Su respuesta a la pregunta