Em um script que estou escrevendo, quero criar algo temporário no meu sistema de arquivos, mas não está lá, /tmp
mas em outro lugar, e pode não ser um arquivo nem um diretório (por exemplo, talvez seja um pipe nomeado ou um link simbólico). A questão é que terei que criar tudo sozinho. Agora, quero usar um nome de arquivo exclusivo para o meu temporário, para que futuras invocações do utilitário, além de qualquer outro código em execução, não tentem usar o mesmo nome.
Se eu estivesse apenas criando um arquivo ou diretório temporário em /tmp
, eu poderia usar mktemp
. Mas o que eu faço quando quero apenas gerar o nome?
Um requisito um tanto conflitante! Em termos linguísticos , você coloca arquivos exclusivos em
$TMPDIR
(ou /tmp por padrão), ou eles são arquivos portadores de estado , e nesse caso eles pertencem a$XDG_STATE_HOME/yourapplicationname/
(que por padrão é$HOME/.local/state/yourapplicationname/
).Essa é uma péssima ideia! Deixe
mktemp
o arquivo ser criado e, em seguida, sobrescreva-o com o que você quiser. Deixar quemktemp
o arquivo seja criado garante que você esteja usando algo único que nunca foi usado antes¹, por exemplo, por uma execução interrompida do seu script ou por um segundo script.Em ambos os casos, você usa
mktemp
, com a opção-p
, por exemplo, algo comoMas esse é um nome de arquivo temporário seguro, e não é exatamente o mesmo que um nome de arquivo único. Para isso, normalmente você precisaria de um UUID (identificador universal único) e pode gerar um usando
uuidgen
. Mas: ao contrário demktemp
, isso não é seguro para raça. Parece que você quermktemp
, de verdade!¹ Para seu interesse técnico, por que esta é provavelmente a única maneira de criar um nome de arquivo seguro contra colisões, mesmo ao executar várias instâncias do seu script:
mktemp
cria esse arquivo temporário chamandoopenat(AT_FDCWD, "/path/to/random/filename", O_RDWR|O_CREAT|O_EXCL)
.E a parte emocionante aqui é
O_RDWR|O_CREAT|O_EXCL
:O_RDWR
garante que o arquivo só será criado se o resultado puder ser lido e gravado.O_CREAT
permite a criação do arquivo abrindo-o,O_EXCL
impõe que este arquivo seja criado; se ele existia antes,openat
falha emktemp
escolhe um nome de arquivo diferente.A API do sistema de arquivos UNIX garante que duas tarefas, mesmo em execução exatamente síncrona, não possam
O_EXCL
abrir o mesmo nome de arquivo – portanto, essa é a única maneira fornecida pelo sistema operacional de garantir que um arquivo seja criado apenas uma vez. Qualquer outra coisa é uma condição de corrida!Então, tanto corretamente quanto em termos idiomáticos : use
mktemp
para criar um arquivo temporário no qual você então escreve. Essa é a maneira UNIX de fazer! Não crie você mesmo.Felizmente, isso não é totalmente verdade. Além disso
openat(… , O_CREAT|O_EXCL)
, a chamada de sistema tambémrenameat
é atômica.Então, a dança corrida-condição-segurança se torna, se você estiver em um script de shell:
filename="$(mktemp -p "${directory}")"
para "reservar" o nome do arquivo, com segurançaln -f -s -- whatever "${filename}"
faça isso corretamente, atomicamente (criando um link simbólico com nome aleatório erenameat
aplicando - a ele${filename}
). Suspeito que seja isso que você quer aqui.primeiro, você cria um diretório temporário local com segurança no mesmo sistema de arquivos,
localtmp="$(mktemp -d -p "${directory}")"
,então você cria um pipe nomeado lá,
mkfifo -- "${localtmp}/fifo"
,então você o renomeia para o destino
mv -- "${localtmp}/fifo" "${filename}"
e,finalmente, você remove o diretório temporário vazio
rmdir -- "${directory}"
Pronto! É verdade que dá um pouco mais de trabalho, mas é assim que você pode nomear qualquer coisa aleatoriamente em um script de shell, então valeu a pena anotar :)
Bem, você quer um nome único garantido ou um nome probabilisticamente único? Se for a segunda opção, então sim, gere um nome aleatório usando qualquer significado, use-o e continue com o trabalho. Mas isso deixa a possibilidade, por menor que seja, de que outra cópia da mesma ferramenta (ou até mesmo uma não relacionada) possa acabar usando o mesmo arquivo, se por acaso gerar exatamente o mesmo nome.
Para garantir a unicidade, você precisa criar o objeto e verificar se ele foi criado novamente. O sistema operacional pode fazer isso com segurança, desde que você solicite, por exemplo, a
open()
chamada de sistema com os sinalizadoresO_CREAT
andO_EXCL
falha se um arquivo com o mesmo nome já existir. O mesmo vale paramkfifo()
andmkdir()
.Então, se você quiser um FIFO, pode gerar um nome, executar
mkfifo
uma verificação se for bem-sucedido e tentar novamente com um novo nome se falhar. Mas fazer isso manualmente é um pouco cansativo. Para arquivos comuns, elemktemp
faz isso por você, mas não cria FIFOs.No entanto, ele pode criar um diretório exclusivo para você, e então você pode criar quaisquer arquivos/links simbólicos/FIFOs dentro desse diretório, sabendo que todo o diretório tem um nome exclusivo.
Então:
Se você também não conseguir criar o diretório intermediário, precisará aceitar a possibilidade de uma colisão ou fazer a verificação manualmente:
Da
mktemp
página do manual...E você não pode garantir o futuro.
mktemp
tem a seguinte opção:Por exemplo, obtive apenas um caminho temporário sem criação usando:
O motivo pelo qual a
--dry-run
opção não é segura é que ela não garante que dois processos não tentem usar o mesmo caminho temporário.Considerando que a
--tmpdir
opção especifica o diretório temporário, substituindo o padrão de/tmp
, pode ser mais seguro usá-la--tmpdir
para permitirmktemp
a criação de um caminho temporário de sua escolha de forma segura.A verdadeira maneira idiomática (no Unix) de gerar um nome de arquivo temporário exclusivo é usar o ID do processo atual para formar o nome.
Por exemplo, em um script de shell:
Só pode haver um processo com um determinado ID em execução ao mesmo tempo, portanto o ID do processo é exclusivo durante toda a vida útil desse processo.
Em geral, isso costuma ser considerado como tendo "singularidade" suficiente para qualquer arquivo temporário , pois a maioria desses arquivos terá apenas uma vida útil igual à do processo que os cria e os utiliza.
De fato, se o seu arquivo for realmente temporário e for usado apenas enquanto o seu processo (script) estiver em execução, mesmo que haja um arquivo obsoleto com o mesmo nome, você pode usá-lo como se tivesse sido criado pelo seu processo, pois é impossível que o processo anterior que o criou ainda o esteja usando. Esse processo não está mais em execução, pois o seu processo atual agora tem o mesmo ID de processo e, portanto, esse arquivo, mesmo que existisse anteriormente, ainda é "único", embora talvez precise de alguma limpeza, como truncamento, se for um arquivo comum.
Claro que se um determinado usuário só puder executar uma instância do seu script por vez, e ele o executar de forma que o arquivo temporário seja criado em um diretório privado onde somente ele poderá gravar, então incluir o ID do processo no nome do arquivo temporário não faz sentido, mas as pessoas provavelmente considerarão seu código suspeito se você não incluir o ID do processo.
O restante disto é uma digressão sobre possíveis problemas de segurança que podem ou não afetar seu script:
Usar um nome "previsível", por exemplo, um que depende do ID do processo para exclusividade, nem sempre é considerado seguro, especialmente se o diretório onde o arquivo é criado tiver mais permissões do que deveria (ou for um diretório compartilhado com permissões intencionalmente não privadas, por exemplo
/tmp
), pois algum invasor pode ter criado o arquivo de destino com permissões ligeiramente diferentes das que você pretende, ou até mesmo como um link simbólico apontando para um arquivo sensível, etc., etc., etc.Já que você está perguntando sobre scripts de shell, a maneira mais segura de garantir que um arquivo de algum tipo arbitrário seja único e criado com segurança como único é criá-lo em um diretório com nome exclusivo, já que diretórios podem ser criados com segurança com nomes exclusivos a partir de um script de shell (e presumivelmente com permissões privadas, desde que uma senha segura
umask
esteja definida). Dessa forma, seu script também pode evitar com segurança potenciais ataques "Time of Check(creation) vs. Time Of Use" (TOCTOU). Basta adicionar um número de "edição" ao caminho se omkdir(1)
erro falhar e tentar novamente. Ainda existe o risco de um invasor realizar um ataque de negação de serviço contra seu script...Então, é claro que a expressão moderna é de fato usar
mktemp(1)
, com sua-d
opção para seus propósitos, já que isso lhe dará umumask
ambiente seguro, protegido e, claro, único e privado (mesmo com um ) inseguro para criar qualquer tipo de arquivo que desejar. Você pode evitar isso/tmp
usando a-p dir
opção , onde, claro, dir pode ser apenas ".
" se for o que você precisa.mktemp(1)
cuida para garantir que o resultado seja único e não entre em conflito com quaisquer sobras obsoletas.Nenhuma frase de efeito? Inacreditável!
-c6
6 letras ou 6^62 possibilidades.