A descrição na open(2)
página do manual fornece algumas dicas para começar:
O_PATH (since Linux 2.6.39)
Obtain a file descriptor that can be used for two purposes:
to indicate a location in the filesystem tree and to per‐
form operations that act purely at the file descriptor
level. The file itself is not opened, and other file oper‐
ations (e.g., read(2), write(2), fchmod(2), fchown(2),
fgetxattr(2), ioctl(2), mmap(2)) fail with the error EBADF.
Às vezes, não queremos abrir um arquivo ou diretório. Em vez disso, queremos apenas uma referência a esse objeto do sistema de arquivos para executar determinadas operações (por exemplo, fchdir()
para um diretório referido por um descritor de arquivo que abrimos usando O_PATH
). Portanto, um ponto trivial: se esse é o nosso objetivo, abrir com O_PATH
deve ser um pouco mais barato, pois o arquivo em si não é realmente aberto.
E um ponto menos trivial: antes da existência de O_PATH
, a maneira de obter tal referência a um objeto do sistema de arquivos era abrir o objeto O_RDONLY
. Mas o uso de O_RDONLY
requer que tenhamos permissão de leitura no objeto. No entanto, existem vários casos de uso em que não precisamos realmente ler o objeto: por exemplo, executar um binário ou acessar um diretório ( fchdir()
) ou acessar um diretório para tocar em um objeto dentro do diretório.
Uso com chamadas do sistema "* at ()"
O comum, mas não o único, o uso de O_PATH
é abrir um diretório, a fim de ter uma referência a esse diretório para uso com o "* a" chamadas de sistema, como openat()
, fstatat()
, fchownat()
e assim por diante. Esta família de chamadas de sistema, que podemos mais ou menos pensam como os sucessores modernos para as chamadas de sistema mais velhos com nomes semelhantes ( open()
, fstat()
, fchown()
e assim por diante), servem para dois propósitos, o primeiro dos quais você tocar em quando você perguntar " por que eu quero usar um descritor de arquivo em vez do caminho do diretório? ". Se olharmos mais abaixo na open(2)
página de manual, encontramos este texto (em um subtítulo com a justificativa para as chamadas do sistema "* at"):
First, openat() allows an application to avoid race conditions
that could occur when using open() to open files in directories
other than the current working directory. These race conditions
result from the fact that some component of the directory prefix
given to open() could be changed in parallel with the call to
open(). Suppose, for example, that we wish to create the file
path/to/xxx.dep if the file path/to/xxx exists. The problem is
that between the existence check and the file creation step, path
or to (which might be symbolic links) could be modified to point
to a different location. Such races can be avoided by opening a
file descriptor for the target directory, and then specifying that
file descriptor as the dirfd argument of (say) fstatat(2) and ope‐
nat().
Para tornar isso mais concreto ... Suponha que tenhamos um programa que deseja executar várias operações em um diretório que não seja o diretório de trabalho atual, o que significa que devemos especificar algum prefixo de diretório como parte dos nomes de arquivos que usamos. Suponha, por exemplo, que o nome do caminho seja /dir1/dir2/file
e desejemos executar duas operações:
- Execute algumas verificações
/dir1/dir2/file
(por exemplo, quem possui o arquivo ou a que horas foi modificado pela última vez).
- Se estivermos satisfeitos com o resultado dessa verificação, talvez desejemos fazer alguma outra operação do sistema de arquivos no mesmo diretório, por exemplo, criando um arquivo chamado
/dir1/dir2/file.new
.
Agora, suponha que fizemos tudo usando chamadas de sistema tradicionais baseadas em nomes de caminhos:
struct stat stabuf;
stat("/dir1/dir2/file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
fd = open("/dir1/dir2/file.new", O_CREAT | O_RDWR, 0600);
/* And then populate file referred to by fd */
}
Agora, além disso, suponha que no prefixo do diretório /dir1/dir2
um dos componentes (digamos dir2
) fosse realmente um link simbólico (que se refere a um diretório) e que entre a chamada para stat()
e a chamada paraopen()
uma pessoa mal-intencionada fosse possível alterar o destino do link simbólico dir2
para apontar para um diretório diferente. Essa é uma condição clássica de corrida no momento do check-in-time. Nosso programa verificou um arquivo em um diretório, mas foi levado a criar um arquivo em um diretório diferente - talvez um diretório sensível à segurança. O ponto principal aqui é que o nome do caminho /dir/dir2
parecia o mesmo, mas o que se refere mudou completamente.
Podemos evitar esse tipo de problema usando as chamadas "* at". Primeiro, obtemos um identificador referente ao diretório em que faremos nosso trabalho:
dirfd = open("/dir/dir2", O_PATH);
O ponto crítico aqui é que dirfd
é uma referência estável ao diretório que foi referido pelo caminho /dir1/dir2
no momento da open()
chamada. Se o destino do link simbólico dir2
for alterado posteriormente, isso não afetará o que dirfd
se refere. Agora, podemos fazer nossa operação de verificação + usando as chamadas "* at" equivalentes às chamadas stat()
e open()
acima:
fstatat(dirfd, ""file", &statbuf)
struct stat stabuf;
fstatat(dirfd, "file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
fd = openat(dirfd, "file.new", O_CREAT | O_RDWR, 0600);
/* And then populate file referred to by fd */
}
Durante essas etapas, qualquer manipulação de links simbólicos no nome do caminho /dir/dir2
não terá impacto: a verificação ( fstatat()
) e a operação ( openat()
) são garantidas no mesmo diretório.
Há outro propósito em usar as chamadas "* at ()", relacionadas à idéia de "diretórios de trabalho atuais por thread" em programas multithread (e novamente poderíamos abrir os diretórios usando O_PATH
), mas acho que esse uso provavelmente é menos relevante para sua pergunta e deixo que você leia a open(2)
página de manual, se quiser saber mais.
Uso com descritores de arquivo para arquivos regulares
Um uso de O_PATH
arquivos regulares é abrir um binário para o qual temos permissão de execução (mas não necessariamente permissão de leitura, para que não possamos abrir o arquivo O_RDONLY
). Esse descritor de arquivo pode ser passado fexecve(3)
para executar o programa. Tudo o que fexecve(fd, argv, envp)
está fazendo com seu fd
argumento é essencialmente:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(Embora, começando com glibc 2.27, a implementação faça uso da execveat(2)
chamada do sistema, nos kernels que fornecem essa chamada do sistema.)