Um 'unlink' ou 'renomear' portável e atomicamente faz um 'link' falhar?

2

Pergunta

Suponha que eu tenha algum diretório não (arquivo, named pipe / socket, whatever) no caminho /tmp/foo e algum outro diretório não no caminho /tmp/bar . Em seguida, dois (ou mais) processos começam a ser executados simultaneamente:

O processo um faz:

unlink('/tmp/foo') /* or rename('/tmp/foo', '/tmp/removed') */
unlink('/tmp/bar') /* or rename('/tmp/bar', '/tmp/removed') */

Processo dois (e assim por diante):

link('/tmp/foo', '/tmp/bar')

Pelo que entendi, não há como o processo dois ter êxito (o link(2) é tentado enquanto /tmp/foo ainda está presente, caso em que /tmp/bar também está presente, portanto, ele deve falhar com EEXIST ou /tmp/foo acabou, portanto, deve falhar com ENOENT ).

Mas essa intuição baseia-se na suposição de que as chamadas do sistema unlink(2) e / ou rename(2) são inerentemente sequenciais em seus efeitos de desvinculação, então estou procurando uma verificação do meu entendimento: Existe algum sistema semelhante a nix? lá cujo kernel permite que as duas chamadas unlink(2) e / ou rename(2) sejam bem-sucedidas, mas simultaneamente faz com que link(2) seja bem-sucedido (seja para reordenar a desvinculação de /tmp/foo e /tmp/bar e não abstrair / escondendo isso do processo chamando link(2) , ou através de alguma outra condição peculiar de corrida / bug)?

Compreensão atual

Eu li as páginas de manual para unlink(2) , rename(2) e link(2) para Linux e alguns BSDs, e a especificação POSIX para essas funções. Mas eu não acho que eles realmente contenham algo reconfortante sobre este assunto, após cuidadosa consideração. Pelo menos com rename(2) , nos é prometido que o destino é atomicamente substituído se já estiver presente ( bugs no próprio sistema operacional, ), mas nada mais.

Eu vi reivindicações que várias execuções simultâneas de rename(foo, qux) atomicamente e portavelmente terão todos menos uma renomeação com ENOENT - então isso é promissor! Não tenho certeza se isso pode ser estendido para ter um link(foo, bar) com ENOENT nas mesmas circunstâncias.

Respostas preferenciais

Eu percebo que esta é uma daquelas situações que "não podem ser negativas" - nós podemos, na melhor das hipóteses, apenas notar que não há evidência de que um sistema similar a nix que permitirá que o processo link(2) tenha sucesso existe .

Então, o que estou procurando são respostas cobrindo o maior número possível de sistemas * nix (pelo menos Linux, OS X e os vários BSDs, mas idealmente também os sistemas proprietários que ainda estão em uso, como o Solaris 10) - de pessoas que têm familiaridade suficiente com esses sistemas e com este conjunto restrito de problemas (operações de sistema de arquivos atômicas / bem ordenadas) que eles estão confiantes (tanto quanto um realisticamente pode ser) que eles saberiam de questões como o já mencionado problema do Mac OS X rename(2) -não-realmente-atómico se eles existissem nas plataformas com as quais estão familiarizados. Isso me daria confiança suficiente de que isso funciona da maneira que eu acho que faz de uma maneira portátil o suficiente para confiar.

Nota final

Esta não é uma questão "problema X / Y" - não há nenhum problema subjacente que possa ser respondido consultando-me os vários mecanismos de bloqueio / IPC ou qualquer outra coisa que trabalhe em torno da incerteza sobre como essas chamadas de sistema interagem: Eu quero especificamente saber se alguém pode confiar nas chamadas de sistema acima, interagindo portualmente como esperado em sistemas semelhantes a nix em uso prático hoje.

    
por mtraceur 02.10.2016 / 22:33

1 resposta

5

Veja padrões como POSIX para garantias de portabilidade. Na prática, a maioria dos sistemas compatíveis com POSIX apresenta pequenos desvios em relação às especificações, mas, em geral, você pode confiar nas garantias dadas na especificação. A maioria dos unices modernos está em conformidade com a especificação, mesmo que não tenham sido formalmente testados. Eles podem precisar ser executados em um modo POSIX, por exemplo definindo POSIXLY_CORRECT=1 com bash ou certificando-se de que /usr/xpg4/bin esteja à frente de /bin e /usr/bin in PATH no Solaris.

Single Unix v2 (uma antiga extensão do POSIX) tem isto a dizer sobre link :

The link() function will atomically create a new link for the existing file and the link count of the file is incremented by one.

Sobre rename :

If the link named by the new argument exists, it is removed and old renamed to new. In this case, a link named new will remain visible to other processes throughout the renaming operation and will refer either to the file referred to by new or old before the operation began.

POSIX declara explicitamente que, se o destino existir, sua substituição deve ser atômica. No entanto, ele não declara que o próprio renomeamento deve ser atômico, ou seja, que não há ponto no tempo em que ambos antigo e novo se refiram ao arquivo em questão, ou quando nenhum faz. Na prática, essas propriedades são verdadeiras em sistemas unix, pelo menos com sistemas de arquivos locais.

Além disso, a ordem das operações é garantida: em C, ; garante a execução sequencial; em sh, ; / newline garante execução sequencial (assim como && e assim por diante); outras linguagens de programação oferecem garantias semelhantes. Então, em

unlink("/tmp/foo");
unlink("/tmp/bar");

é garantido que não há ponto no tempo em que /tmp/foo exista, mas não /tmp/bar (assumindo que /tmp/bar exista inicialmente). Portanto, um processo simultâneo executando link("/tmp/foo", "/tmp/bar") não pode ser bem-sucedido.

Note que a atomicidade não garante resiliência . A atomicidade é sobre o comportamento observável em um sistema ao vivo. A resiliência, no contexto dos sistemas de arquivos, é sobre o que acontece em caso de falha do sistema. Muitos sistemas de arquivos sacrificam a resiliência por desempenho, portanto, se a execução de unlink("foo"); unlink("bar"); for interrompida (com o diretório atual no armazenamento em disco), é possível que bar seja excluído e foo permaneça atrasado.

Alguns sistemas de arquivos de rede dão menos garantias quando ocorrem operações em diferentes clientes. Implementações mais antigas do NFS eram notórias para isso. Eu acho que as implementações modernas são melhores, mas eu não tenho experiência do NFS moderno.

    
por 02.10.2016 / 23:16