Portanto, uma parte bastante simples do programa para Windows. Há um arquivo contendo várias entradas. E eles precisam ser filtrados de uma certa maneira.
A solução é bastante simples - abra o arquivo, leia os registros um por um, escrevemos os necessários em um arquivo temporário. Feche o arquivo Nós o excluímos. Renomeie o temporário para o original. Tudo é tão simples que eu nem vou dar o código. Essa é realmente uma razão suficiente para o artigo?
Enquanto tudo funciona, não há realmente nenhuma razão para escrever sobre isso. Mas, de repente, um dia "tudo cai", porque renomear não ocorre devido a um erro de acesso negado. Isso acontece muito raramente, mas com muito mais frequência suspeitar dos raios cósmicos.
Começamos a escavação. A primeira pista encontrada: no processo de escavação, esta citação da documentação da Microsoft alertou:
A função DeleteFile marca um arquivo para exclusão ao fechar. Portanto, a exclusão do arquivo não ocorre até que o último identificador do arquivo seja fechado. As chamadas subseqüentes ao CreateFile para abrir o arquivo falham com ERROR_ACCESS_DENIED.
Em termos de sintomas, é muito parecido, mas de onde vêm esses "outros manipuladores", se, além de nós, ninguém faz e não deve fazer nada com esse arquivo? E não temos outros tópicos com tópicos que fariam algo com esse arquivo?
O culpado foi encontrado graças ao SysInternals e seu ProcessMonitor. Nós o iniciamos, instalamos o filtro em nosso arquivo de longa duração e tentamos reproduzi-lo por um longo e persistente. Nós reproduzimos. Nós olhamos. E o que vemos lá?
01.2: 30: 28.3162097 PM our_prog.exe 1288 CreateFile our.file SUCESSO Acesso desejado: leitura / gravação genérica
02. 2: 25: 28.3164513 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 0, Comprimento: 898, Prioridade: Normal
...
34.2: 25: 28.3173405 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 35.290, Comprimento: 1.113
35.2: 25: 28.3173493 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 36.403, Comprimento: 1.128
36.2: 25: 28.3173736 PM our_prog.exe 1288 FlushBuffersFile our.file SUCCESS
37.2: 25: 28.3174212 PM our_prog.exe 1288 WriteFile our.file SUCCESS Offset: 0, Length: 40.960,
38.2: 25: 28.3175927 PM Explorer.EXE 1884 QueryBasicInformationFile our.file SUCCESS
39.2: 25: 28.3176144 PM Explorer.EXE 1884 FecharFile our.file SUCESSO
40.2: 25: 28.3263642 PM Explorer.EXE 1884 CreateFile our.file SUCESSO Acesso desejado: atributos de leitura,
41.2: 25: 28.3294990 PM our_prog.exe 1288 FecharFile our.file SUCCESS
42.2: 25: 28.3351356 PM our_prog.exe 1288 CreateFile our.file SUCESSO Acesso desejado: ler atributos, excluir,
43.2: 25: 28.3351856 PM our_prog.exe 1288 QueryAttributeTagFile our.file SUCESSO Atributos: A, ReparseTag: 0x0
44.2: 25: 28.3352020 PM our_prog.exe 1288 SetDispositionInformationFile our.file SUCCESS Excluir: True
45.2: 25: 28.3352218 PM our_prog.exe 1288 FecharFile our.file SUCCESS
46.2: 25: 28.3358275 PM our_prog.exe 1288 CreateFile our.file DELETE PENDING Acesso desejado: leitura / gravação genérica,
47.2: 25: 28.3362207 PM our_prog.exe 1288 CreateFile our.file DELETE PENDING Acesso desejado: leitura / gravação genérica,
48.2: 25: 28.3367696 PM Explorer.EXE 1884 QueryBasicInformationFile our.file SUCCESS
49.2: 25: 28.4279152 PM Explorer.EXE 1884 FecharFile our.file SUCESSO
50.2: 25: 28.4282859 PM Explorer.EXE 1884 CreateFile our.file NOME NÃO ENCONTRADO Acesso desejado: atributos de leitura,
...
83.2: 25: 29.3497760 PM our_prog.exe 1288 CreateFile our.file SUCESSO Acesso desejado: leitura / gravação genérica,
E vemos o seguinte lá (dados extras foram excluídos para não desorganizar). Linhas 1 a 36 - criamos um arquivo, escrevemos nele, fazemos flush. O mais interessante começa nas linhas 38-40. Explorer.exe aparece do nada e começa a ler nosso arquivo.
Na linha 41, fechamos nosso arquivo. Na linha 42 - excluir. E como o explorer.exe ainda está lendo, o arquivo não é excluído. O que podemos ver nas linhas 46 e 47 quando tentamos renomear nosso arquivo temporário para o principal (status DELETE PENDING PENDING em vez de SUCESSO).
Explorer.exe termina a leitura apenas na linha 49. Somente nesse momento o arquivo é fisicamente excluído (como a linha 50 indica indiretamente, onde nosso persister explorer.exe novamente tenta abrir o arquivo para leitura, mas falha porque o arquivo não mais).
O que se segue disso? Graças ao Microsoft Windows, mesmo uma operação simples de exclusão de arquivos agora precisa ser feita no estilo de programação paranóica. Eles chamaram a função de exclusão, certificaram-se de que ela retornou OK e entraram no ciclo de espera "até que o arquivo já tenha sido fisicamente excluído". Bem, sim, sem saber que "ela tem um néon por dentro", quase nada pode ser feito ...
Mas agora a dúvida está me atormentando de que tal abordagem é uma prática geralmente aceita. Para ter em mente, durante qualquer trabalho com arquivos no Windows, que o SO, a qualquer momento, decida fazer algo com os arquivos sem o seu conhecimento e escreva um código resistente a isso. Nesse contexto, a pesquisa é menor.