Pregunta ¿Qué sucede con un manejador de archivo abierto en Linux si el archivo apuntado se mueve, eliminar


¿Qué sucede con un manejador de archivo abierto en Linux si el archivo apuntado mientras tanto obtiene:

  • Movido lejos -> ¿El archivo maneja permanece válido?
  • Eliminado -> ¿Esto lleva a un EBADF, lo que indica un identificador de archivo no válido?
  • Reemplazado por un nuevo archivo -> ¿Se maneja el archivo apuntando a este nuevo archivo?
  • Reemplazado por un enlace fijo a un archivo nuevo -> ¿Mi archivo maneja "seguir" este enlace?
  • Reemplazado por un enlace suave a un nuevo archivo -> ¿Mi archivo manejar golpear este archivo de enlace suave ahora?

Por qué hago estas preguntas: estoy usando hardware conectado en caliente (como dispositivos USB, etc.). Puede suceder que el dispositivo (y también su / dev / archivo) sea reajustado por el usuario u otro Gremlin.

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


74
2018-01-08 16:09


origen


Respuestas:


Si el archivo se mueve (en el mismo sistema de archivos) o se lo renombra, el controlador de archivo permanece abierto y aún se puede usar para leer y escribir el archivo.

Si se elimina el archivo, el manejador de archivo permanece abierto y aún se puede usar (Esto no es lo que algunos 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 eliminar (ver arriba), es decir, el manejador de archivo continuará refiriéndose al original versión 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 moverlo, renombrarlo 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, solo elimina el enlace del directorio.


Si, por otro lado, el dispositivo subyacente desaparece (por ejemplo, USB se desconecta), entonces el manejador de archivos ya no será válido y es probable que dé I / error en cualquier operación. Aún así, tienes que cerrarlo. Esto será cierto incluso si el dispositivo está conectado de nuevo, ya que no es prudente mantener un archivo abierto en este caso.


110
2018-01-08 22:09



Los identificadores de archivo apuntan a un inodo y no a una ruta, por lo que la mayoría de los escenarios siguen funcionando como usted supone, ya que el identificador aún apunta al archivo.

Específicamente, con el escenario de eliminación, la función se llama "desvincular" por un motivo, destruye un "enlace" entre un nombre de archivo (un dentry) y un archivo. Cuando abre un archivo, luego lo desenlaza, el archivo en realidad todavía existe hasta que su recuento de referencias se pone 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 no podrá acceder a él, incluso si el dispositivo vuelve. Deberá cerrar el dispositivo y volver a abrirlo.


6
2018-01-08 16:13



No estoy seguro de las otras operaciones, pero en cuanto a la eliminación: la eliminación simplemente no tiene lugar (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 de su aplicación.

Algunas aplicaciones (que no vienen a la mente) se basan en este comportamiento, al crear, abrir y eliminar de inmediato archivos, que luego viven exactamente el mismo tiempo que la aplicación, lo que permite que otras aplicaciones conozcan el ciclo de vida de la primera aplicación sin necesidad de mira los mapas de proceso y tal.

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


2
2018-01-08 16:14



si desea verificar si el manejador 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;
}

2
2018-02-05 09:47



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

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


1
2018-01-08 16:13



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

Finalmente encontrará un enlace simbólico a su dispositivo (en / 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 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 de enlace.

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

1
2018-01-08 20:57



El siguiente experimento muestra que La respuesta de MarkR es correcto.

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

Utilizar gcc code.c para producir a.out. correr ./a.out. Cuando vea el siguiente resultado:

line: 1234

Utilizar rm data borrar data. Pero ./a.out continuará ejecutándose sin errores y producirá la siguiente salida completa:

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

He hecho el experimento en Ubuntu 16.04.3.


0
2018-05-21 10:09