O que acontece com um identificador de arquivo aberto no Linux se o arquivo apontado for movido ou excluído

107

O que acontece com um identificador de arquivo aberto no Linux se o arquivo apontado obtiver:

  • Movido -> O identificador de arquivo permanece válido?
  • Excluído -> Isso leva a um EBADF, indicando um identificador de arquivo inválido?
  • Substituído por um novo arquivo -> O identificador de arquivo está apontando para este novo arquivo?
  • Substituído por um link físico para um novo arquivo -> Meu arquivo "segue" este link?
  • Substituído por um link simbólico para um novo arquivo -> Meu identificador de arquivo acessou este arquivo de link simbólico agora?

Por que estou fazendo essas perguntas: estou usando hardware hot-plugged (como dispositivos USB, etc.). Pode acontecer que o dispositivo (e também seu / dev / arquivo) seja reanexado pelo usuário ou outro Gremlin.

Qual é a melhor prática para lidar com isso?

Maus
fonte

Respostas:

159

Se o arquivo for movido (no mesmo sistema de arquivos) ou renomeado, o identificador de arquivo permanecerá aberto e ainda poderá ser usado para ler e gravar o arquivo.

Se o arquivo for excluído, o identificador do arquivo permanece aberto e ainda pode ser usado (isso não é o que algumas pessoas esperam). O arquivo não será realmente excluído até que o último identificador seja fechado.

Se o arquivo for substituído por um novo arquivo, isso depende exatamente de como. Se o conteúdo do arquivo for sobrescrito, o identificador do arquivo ainda será válido e acessará o novo conteúdo. Se o arquivo existente for desvinculado e um novo criado com o mesmo nome ou, se um novo arquivo for movido para o arquivo existente usando rename(), é o mesmo que exclusão (veja acima) - isto é, o identificador de arquivo continuará a se referir a a versão original do arquivo.

Em geral, uma vez que o arquivo é aberto, o arquivo está aberto, e ninguém que muda a estrutura do diretório pode mudar isso - eles podem mover, renomear o arquivo ou colocar qualquer outra coisa em seu lugar, ele simplesmente permanece aberto.

No Unix, não há exclusão, apenas unlink(), o que faz sentido, pois não necessariamente exclui o arquivo - apenas remove o link do diretório.


Se, por outro lado, o dispositivo subjacente desaparecer (por exemplo, desconexão USB), o identificador de arquivo não será mais válido e provavelmente apresentará E / S / erro em qualquer operação. Você ainda tem que fechá-lo. Isso vai ser verdade mesmo se o dispositivo estiver conectado novamente, pois não é sensato manter um arquivo aberto neste caso.

MarkR
fonte
Suponho que seu segundo ponto se aplica igualmente se um diretório que contém o arquivo for excluído. É assim mesmo?
Drew Noakes em
2
Estou interessado em uma coisa: se você usar o comando cp para sobrescrever um arquivo, é o primeiro caso ou o segundo caso?
xuhdev
1
" O arquivo não será realmente excluído até que o último identificador seja fechado. " Interessante. obrigado
Geremia
8

Os identificadores de arquivo apontam para um inode e não para um caminho, portanto, a maioria dos seus cenários ainda funcionam como você supõe, pois o identificador ainda aponta para o arquivo.

Especificamente, com o cenário de exclusão - a função é chamada de "desvincular" por uma razão, ela destrói um "vínculo" entre um nome de arquivo (dentry) e um arquivo. Quando você abre um arquivo e, em seguida, desvincula-o, o arquivo realmente ainda existe até que sua contagem de referência chegue a zero, que é quando você fecha o identificador.

Editar: No caso de hardware, você abriu um identificador para um nó de dispositivo específico, se você desconectar o dispositivo, o kernel falhará em todos os acessos a ele, mesmo se o dispositivo voltar. Você terá que fechar o dispositivo e reabri-lo.

Ana betts
fonte
5

Não tenho certeza sobre as outras operações, mas quanto à exclusão: a exclusão simplesmente não ocorre (fisicamente, ou seja, no sistema de arquivos) até que o último identificador aberto para o arquivo seja fechado. Portanto, não deve ser possível excluir um arquivo do seu aplicativo.

Alguns aplicativos (que não vêm à mente) contam com esse comportamento, criando, abrindo e excluindo imediatamente arquivos, que duram exatamente o mesmo tempo que o aplicativo - permitindo que outros aplicativos estejam cientes do ciclo de vida do primeiro aplicativo sem precisar veja mapas de processos e outros.

É possível que considerações semelhantes se apliquem a outras coisas.

Carl Smotricz
fonte
4

se você quiser verificar se o manipulador de arquivo (descritor de arquivo) está correto, você pode chamar esta função.

/**
 * version : 1.1
 *    date : 2015-02-05
 *    func : check if the fileDescriptor is fine.
 */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

/**
 * On success, zero is returned.  On error, -1  is  returned,  and  errno  is  set
 *      appropriately.
 */
int check_fd_fine(int fd) {
    struct stat _stat;
    int ret = -1;
    if(!fcntl(fd, F_GETFL)) {
        if(!fstat(fd, &_stat)) {
            if(_stat.st_nlink >= 1)
                ret = 0;
            else
                printf("File was deleted!\n");
        }
    }
    if(errno != 0)
        perror("check_fd_fine");
    return ret;
}

int main() {
    int fd = -1;
    fd = open("/dev/ttyUSB1", O_RDONLY);
    if(fd < 0) {
        perror("open file fail");
        return -1;
    }
    // close or remove file(remove usb device)
//  close(fd);
    sleep(5);
    if(!check_fd_fine(fd)) {
        printf("fd okay!\n");
    } else {
        printf("fd bad!\n");
    }
    close(fd);
    return 0;
}
kangear
fonte
1
Qual é o ponto de if(!fcntl(fd, F_GETFL)) {verificação? Eu acho que você está procurando por EBADFlá. (Você provavelmente também se esqueceu de inicializar errnoem 0).
woky
Isso não é trabalho para mim. Tentei usar essa abordagem com open(O_WRONLY|O_APPEND)- st_nlink sempre fica> = 1 enquanto meu descritor é aberto.
imbearr
2

As informações na memória de um arquivo excluído (todos os exemplos fornecidos são instâncias de um arquivo excluído), bem como os inodes no disco, permanecem até que o arquivo seja fechado.

Hardware sendo hotplug é uma questão completamente diferente, e você não deve esperar que o seu programa para se manter vivo por muito tempo se os inodes em disco ou metadados mudaram em tudo .

Ignacio Vazquez-Abrams
fonte
2

O seguinte experimento mostra que a resposta do MarkR está correta.

code.c:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <stdio.h>

void perror_and_exit() {
  perror(NULL);
  exit(1);
}

int main(int argc, char *argv[]) {
  int fd;
  if ((fd = open("data", O_RDONLY)) == -1) {
    perror_and_exit();
  }
  char buf[5];
  for (int i = 0; i < 5; i++) {
    bzero(buf, 5);
    if (read(fd, buf, 5) != 5) {
      perror_and_exit();
    }
    printf("line: %s", buf);
    sleep(20);
  }
  if (close(fd) != 0) {
    perror_and_exit();
  }
  return 0;
}

dados:

1234
1234
1234
1234
1234

Use gcc code.cpara produzir a.out. Corra ./a.out. Quando você vê a seguinte saída:

line: 1234

Use rm datapara excluir data. Mas ./a.outcontinuará a ser executado sem erros e produzirá a seguinte saída completa:

line: 1234
line: 1234
line: 1234
line: 1234
line: 1234

Eu fiz o experimento no Ubuntu 16.04.3.

Jingguo Yao
fonte
1

No diretório / proc / você encontrará uma lista de todos os processos atualmente ativos, apenas encontre seu PID e todos os dados relacionados a ele. Uma informação interessante é a pasta fd /, você encontrará todos os gerenciadores de arquivos atualmente abertos pelo processo.

Eventualmente você encontrará um link simbólico para o seu dispositivo (em / dev / ou mesmo / proc / bus / usb /), se o dispositivo travar o link ficará inativo e será impossível atualizar este identificador, o processo deve ser fechado e abra-o novamente (mesmo com a reconexão)

Este código pode ler o status atual do link do seu PID

#include <unistd.h>
#include <stdio.h>
#include <dirent.h>

int main() {
    // the directory we are going to open
    DIR           *d;

    // max length of strings
    int maxpathlength=256;

    // the buffer for the full path
    char path[maxpathlength];

    // /proc/PID/fs contains the list of the open file descriptors among the respective filenames
    sprintf(path,"/proc/%i/fd/",getpid() );

    printf("List of %s:\n",path);

    struct dirent *dir;
    d = opendir(path);
    if (d) {
        //loop for each file inside d
        while ((dir = readdir(d)) != NULL) {

            //let's check if it is a symbolic link
            if (dir->d_type == DT_LNK) {

                const int maxlength = 256;

                //string returned by readlink()
                char hardfile[maxlength];

                //string length returned by readlink()
                int len;

                //tempath will contain the current filename among the fullpath
                char tempath[maxlength];

                sprintf(tempath,"%s%s",path,dir->d_name);
                if ((len=readlink(tempath,hardfile,maxlength-1))!=-1) {
                    hardfile[len]='\0';
                        printf("%s -> %s\n", dir->d_name,hardfile);

                } else
                    printf("error when executing readlink() on %s\n",tempath);

            }
        }

        closedir(d);
    }
    return 0;
}

Este código final é simples, você pode brincar com a função linkat.

int
open_dir(char * path)
{
  int fd;

  path = strdup(path);
  *strrchr(path, '/') = '\0';
  fd = open(path, O_RDONLY | O_DIRECTORY);
  free(path);

  return fd;
}

int
main(int argc, char * argv[])
{
  int odir, ndir;
  char * ofile, * nfile;
  int status;

  if (argc != 3)
    return 1;

  odir = open_dir(argv[1]);
  ofile = strrchr(argv[1], '/') + 1;

  ndir = open_dir(argv[2]);
  nfile = strrchr(argv[2], '/') + 1;

  status = linkat(odir, ofile, ndir, nfile, AT_SYMLINK_FOLLOW);
if (status) {
  perror("linkat failed");
}


  return 0;
}
Douglas L
fonte