Cosa succede a un handle di file aperto su Linux se il file puntato viene spostato o eliminato


107

Cosa succede a un handle di file aperto su Linux se il file puntato nel frattempo ottiene:

  • Spostato -> L'handle del file rimane valido?
  • Eliminato -> Questo porta a un EBADF, che indica un handle di file non valido?
  • Sostituito da un nuovo file -> Il file gestisce il puntamento a questo nuovo file?
  • Sostituito da un collegamento fisico a un nuovo file -> Il mio file gestisce "segue" questo collegamento?
  • Sostituito da un collegamento software a un nuovo file -> Il mio handle di file ha raggiunto questo file collegamento software ora?

Perché sto facendo queste domande: sto usando hardware hot plug (come dispositivi USB, ecc.). Può succedere che il dispositivo (e anche il suo file / dev /) venga ricollegato dall'utente o da un altro Gremlin.

Qual è la migliore pratica per affrontare questo problema?

Risposte:


159

Se il file viene spostato (nello stesso filesystem) o rinominato, l'handle del file rimane aperto e può ancora essere utilizzato per leggere e scrivere il file.

Se il file viene eliminato, l'handle del file rimane aperto e può ancora essere utilizzato (questo non è ciò che alcune persone si aspettano). Il file non verrà effettivamente eliminato fino alla chiusura dell'ultimo handle.

Se il file viene sostituito da un nuovo file, dipende esattamente come. Se il contenuto del file viene sovrascritto, l'handle del file sarà ancora valido e accederà al nuovo contenuto. Se il file esistente è scollegato e ne viene creato uno nuovo con lo stesso nome o, se un nuovo file viene spostato sul file esistente utilizzando rename(), è uguale all'eliminazione (vedi sopra) - cioè, l'handle del file continuerà a fare riferimento a la versione originale del file.

In generale, una volta che il file è aperto, il file è aperto e nessuno che modifica la struttura della directory può cambiarlo: possono spostare, rinominare il file o mettere qualcos'altro al suo posto, semplicemente rimane aperto.

In Unix non esiste solo l'eliminazione, il unlink()che ha senso in quanto non elimina necessariamente il file, rimuove semplicemente il collegamento dalla directory.


Se d'altra parte il dispositivo sottostante scompare (es. USB scollegamento) allora l'handle del file non sarà più valido ed è probabile che fornisca IO / errore su qualsiasi operazione. Devi comunque chiuderlo. Questo sarà vero anche se il dispositivo viene ricollegato, poiché in questo caso non è sensato mantenere un file aperto.


Suppongo che il tuo secondo punto si applichi allo stesso modo se una directory contenente il file viene eliminata. È così?
Drew Noakes

2
Mi interessa una cosa: se usi il comando cp per sovrascrivere un file, è il primo o il secondo caso?
xuhdev

1
" Il file non verrà realmente cancellato fino alla chiusura dell'ultimo handle. " Interessante. grazie
Geremia

8

Gli handle di file puntano a un inode non a un percorso, quindi la maggior parte dei tuoi scenari funzionano ancora come presumi, poiché l'handle punta ancora al file.

In particolare, con lo scenario di eliminazione: la funzione è chiamata "unlink" per un motivo, distrugge un "collegamento" tra un nome di file (un dentry) e un file. Quando apri un file, quindi lo scolleghi, il file esiste ancora fino a quando il suo conteggio dei riferimenti non arriva a zero, ovvero quando chiudi l'handle.

Modifica: nel caso dell'hardware, hai aperto un handle per un nodo del dispositivo specifico, se scolleghi il dispositivo, il kernel fallirà tutti gli accessi ad esso, anche se il dispositivo ritorna. Dovrai chiudere il dispositivo e riaprirlo.


5

Non sono sicuro delle altre operazioni, ma per quanto riguarda la cancellazione: la cancellazione semplicemente non ha luogo (fisicamente, cioè nel file system) fino a quando non viene chiuso l'ultimo handle aperto del file. Pertanto non dovrebbe essere possibile eliminare un file dalla tua applicazione.

Alcune app (che non vengono in mente) fanno affidamento su questo comportamento, creando, aprendo ed eliminando immediatamente file, che quindi vivono esattamente quanto l'applicazione, consentendo ad altre applicazioni di essere a conoscenza del ciclo di vita della prima app senza che sia necessario guarda le mappe di processo e simili.

È possibile che considerazioni simili si applichino alle altre cose.


4

se vuoi controllare se il gestore di file (descrittore di file) va bene, puoi chiamare questa funzione.

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

1
Qual è il punto di if(!fcntl(fd, F_GETFL)) {controllo? Immagino tu stia cercando EBADFlì. (Probabilmente hai anche dimenticato di inizializzare errnoa 0).
woky

Questo non è un lavoro per me. Ho provato a utilizzare questo approccio con open(O_WRONLY|O_APPEND)- st_nlink rimane sempre> = 1 mentre il mio descrittore è aperto.
imbearr

2

Le informazioni in memoria di un file cancellato (tutti gli esempi che fornisci sono istanze di un file cancellato) così come gli inode su disco rimangono in essere finché il file non viene chiuso.

Hardware essere inserite a caldo è un problema completamente diverso, e non si deve aspettare il vostro programma per rimanere in vita a lungo se gli inode su disco o metadati sono cambiate affatto .


2

Il seguente esperimento mostra che la risposta di MarkR è corretta.

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

dati:

1234
1234
1234
1234
1234

Utilizzare gcc code.cper produrre a.out. Corri ./a.out. Quando vedi il seguente output:

line: 1234

Utilizzare rm dataper eliminare data. Ma ./a.outcontinuerà a funzionare senza errori e produrrà l'intero output seguente:

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

Ho fatto l'esperimento su Ubuntu 16.04.3.


1

Sotto / proc / directory troverai un elenco di tutti i processi attualmente attivi, trova il tuo PID e tutti i dati relativi sono lì. Un'informazione interessante è la cartella fd /, troverai tutti i gestori di file attualmente aperti dal processo.

Alla fine troverai un collegamento simbolico al tuo dispositivo (sotto / dev / o anche / proc / bus / usb /), se il dispositivo si blocca il collegamento sarà morto e sarà impossibile aggiornare questo handle, il processo deve chiudersi e riaprirlo (anche con riconnessione)

Questo codice può leggere lo stato corrente del collegamento 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;
}

Questo codice finale è semplice, puoi giocare con la funzione 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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.