¿Qué sucede con un identificador de archivo abierto en Linux si el archivo señalado se mueve o se elimina?

107

¿Qué sucede con un identificador de archivo abierto en Linux si, mientras tanto, el archivo señalado obtiene:

  • Se mudó -> ¿El identificador del archivo sigue siendo válido?
  • Eliminado -> ¿Esto conduce a un EBADF, lo que indica un identificador de archivo no válido?
  • Reemplazado por un nuevo archivo -> ¿El archivo maneja que apunta a este nuevo archivo?
  • Reemplazado por un enlace fijo a un nuevo archivo -> ¿Mi archivo maneja "seguir" este enlace?
  • Reemplazado por un enlace suave a un nuevo archivo -> ¿Mi identificador de archivo llega a este archivo de enlace suave ahora?

Por qué hago estas preguntas: estoy usando hardware de conexión en caliente (como dispositivos USB, etc.). Puede suceder que el usuario u otro Gremlin vuelva a conectar el dispositivo (y también su archivo / dev /).

¿Cuál es la mejor práctica para lidiar con esto?

Maus
fuente

Respuestas:

159

Si el archivo se mueve (en el mismo sistema de archivos) o se le cambia el nombre, el identificador del archivo permanece abierto y aún se puede usar para leer y escribir el archivo.

Si se elimina el archivo, el identificador de archivo permanece abierto y aún se puede usar (esto no es lo que algunas personas esperan). El archivo no se eliminará realmente hasta que se cierre el último identificador.

Si el archivo se reemplaza por un archivo nuevo, depende exactamente cómo. Si se sobrescribe el contenido del archivo, el identificador del archivo seguirá siendo válido y accederá al nuevo contenido. Si el archivo existente está desvinculado y se crea uno nuevo con el mismo nombre o, si se mueve un archivo nuevo al archivo existente usando rename(), es lo mismo que la eliminación (ver arriba), es decir, el identificador del archivo continuará haciendo referencia a la versión original del archivo.

En general, una vez que el archivo está abierto, el archivo está abierto y nadie que cambie la estructura del directorio puede cambiar eso: pueden mover, cambiar el nombre del archivo o poner algo más en su lugar, simplemente permanece abierto.

En Unix no hay eliminación, solo unlink(), lo cual tiene sentido ya que no necesariamente elimina el archivo, simplemente elimina el enlace del directorio.


Si, por otro lado, el dispositivo subyacente desaparece (por ejemplo, desconexión USB), el identificador de archivo ya no será válido y es probable que dé IO / error en cualquier operación. Aunque todavía tienes que cerrarlo. Esto será cierto incluso si el dispositivo se vuelve a enchufar, ya que no es sensato mantener un archivo abierto en este caso.

MarkR
fuente
Supongo que su segundo punto se aplica igualmente si se elimina un directorio que contiene el archivo. ¿Es eso así?
Drew Noakes
2
Estoy interesado en una cosa: si usa el comando cp para sobrescribir un archivo, ¿es el primer caso o el segundo caso?
xuhdev
1
" El archivo no se eliminará realmente hasta que se cierre el último identificador ". Interesante. gracias
Geremia
8

Los identificadores de archivo apuntan a un inodo, no a una ruta, por lo que la mayoría de sus escenarios aún funcionan como asume, ya que el identificador aún apunta al archivo.

Específicamente, con el escenario de eliminación, la función se llama "desvincular" por una razón, destruye un "vínculo" entre un nombre de archivo (un dentry) y un archivo. Cuando abre un archivo y luego lo desvincula, el archivo todavía existe hasta que su recuento de referencias llega a cero, que es cuando cierra el identificador.

Editar: En el caso del hardware, ha abierto un identificador para un nodo de dispositivo específico, si desconecta el dispositivo, el kernel fallará en todos los accesos, incluso si el dispositivo regresa. Deberá cerrar el dispositivo y volver a abrirlo.

Ana Betts
fuente
5

No estoy seguro de las otras operaciones, pero en cuanto a la eliminación: la eliminación simplemente no se lleva a cabo (físicamente, es decir, en el sistema de archivos) hasta que se cierra el último identificador abierto del archivo. Por lo tanto, no debería ser posible eliminar un archivo debajo de su aplicación.

Algunas aplicaciones (que no me vienen a la mente) se basan en este comportamiento, al crear, abrir y eliminar inmediatamente archivos, que luego viven exactamente tanto como la aplicación, lo que permite que otras aplicaciones conozcan el ciclo de vida de la primera aplicación sin necesidad de hacerlo. mire mapas de procesos y demás.

Es posible que se apliquen consideraciones similares a las otras cosas.

Carl Smotricz
fuente
4

si desea verificar si el controlador de archivos (descriptor de archivo) está bien, puede llamar a esta función.

/**
 * 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
fuente
1
¿Cuál es el punto de if(!fcntl(fd, F_GETFL)) {verificación? Supongo que estás buscando EBADFahí. (Probablemente también olvidó inicializar errnoa 0).
woky
Esto no es un trabajo para mí. He intentado utilizar este enfoque con open(O_WRONLY|O_APPEND)- st_nlink siempre permanece> = 1 mientras mi descriptor está abierto.
imbearr
2

La información en la memoria de un archivo eliminado (todos los ejemplos que da son instancias de un archivo eliminado), así como los inodos en el disco permanecen hasta que se cierra el archivo.

El hardware que se conecta en caliente es un problema completamente diferente, y no debe esperar que su programa permanezca activo por mucho tiempo si los inodos o metadatos en el disco han cambiado en absoluto .

Ignacio Vázquez-Abrams
fuente
2

El siguiente experimento muestra que la respuesta de MarkR es correcta.

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;
}

datos:

1234
1234
1234
1234
1234

Úselo gcc code.cpara producir a.out. Corre ./a.out. Cuando vea el siguiente resultado:

line: 1234

Use rm datapara eliminar data. Pero ./a.outcontinuará ejecutándose sin errores y producirá el siguiente resultado completo:

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

Hice el experimento en Ubuntu 16.04.3.

Jingguo Yao
fuente
1

En el directorio / proc / encontrará una lista de todos los procesos actualmente activos, solo busque su PID y todos los datos relacionados están allí. Una información interesante es la carpeta fd /, encontrará todos los controladores de archivos abiertos actualmente por el proceso.

Eventualmente encontrará un enlace simbólico a su dispositivo (bajo / dev / o incluso / proc / bus / usb /), si el dispositivo se cuelga, el enlace estará muerto y será imposible actualizar este identificador, el proceso debe cerrarse y ábralo de nuevo (incluso con la reconexión)

Este código puede leer el estado actual del enlace de su 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 es simple, puedes jugar con la función 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
fuente