Cosa succede a un handle di file aperto su Linux se il file puntato viene spostato, cancella

Cosa succede a un handle di file aperto su Linux se nel frattempo il file puntato 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 hard link in un nuovo file -> Il mio file gestisce “follow” questo link?
  • Sostituito da un collegamento software a un nuovo file -> Il mio file gestisce questo file di collegamento soft ora?

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

Qual è la migliore pratica che si occupa di questo?

    Se il file viene spostato (nello stesso file system) 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 è quello che alcuni si aspettano). Il file non verrà realmente eliminato fino a quando non viene chiuso l’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à comunque valido e accederà al nuovo contenuto. Se il file esistente è scollegato e uno nuovo creato con lo stesso nome o, se un nuovo file viene spostato sul file esistente usando rename() , equivale alla cancellazione (vedi sopra) – cioè, l’handle del file continuerà per fare riferimento alla versione originale del file.

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

    In Unix non c’è eliminazione, solo unlink() , che ha senso in quanto non elimina necessariamente il file – rimuove solo il collegamento dalla directory.


    Se invece il dispositivo sottostante scompare (ad es. USB Unplug), l’handle del file non sarà più valido ed è probabile che fornisca un errore / I / O su qualsiasi operazione. Devi comunque chiuderlo. Questo sarà vero anche se il dispositivo è collegato di nuovo, in quanto non è ragionevole mantenere un file aperto in questo caso.

    Gli handle di file puntano a un inode non a un percorso, quindi la maggior parte degli scenari funziona ancora come si presuppone, poiché l’handle punta ancora al file.

    Nello specifico, con lo scenario di cancellazione – la funzione è chiamata “scollegamento” per un motivo, distrugge un “collegamento” tra un nome file (una dentatura) e un file. Quando si apre un file, quindi lo si scollega, il file esiste ancora fino a quando il suo conteggio dei riferimenti diventa zero, ovvero quando si chiude l’handle.

    Modifica: nel caso dell’hardware, hai aperto un handle su un nodo dispositivo specifico, se scolleghi il dispositivo, il kernel non riuscirà a tutti gli accessi ad esso, anche se il dispositivo ritorna. Dovrai chiudere il dispositivo e riaprirlo.

    Non sono sicuro delle altre operazioni, ma per quanto riguarda la cancellazione: la cancellazione semplicemente non ha luogo (fisicamente, cioè nel file system) finché non viene chiuso l’ultimo handle aperto del file. Quindi non dovrebbe essere ansible cancellare un file da sotto la tua applicazione.

    Alcune app (che non vengono in mente) si basano su questo comportamento, creando, aprendo e cancellando immediatamente i file, che poi vivono esattamente come l’applicazione – consentendo ad altre applicazioni di essere a conoscenza del ciclo di vita della prima app senza bisogno di guarda le mappe dei processi e così via.

    È ansible che considerazioni simili si applichino alle altre cose.

    se si desidera verificare se il gestore di file (descrittore di file) è a posto, è ansible chiamare questa funzione.

     /** * version : 1.1 * date : 2015-02-05 * func : check if the fileDescriptor is fine. */ #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  /** * 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; } 

    Le informazioni in memoria di un file cancellato (tutti gli esempi forniti sono istanze di un file cancellato) e gli inode su disco rimangono in esistenza fino alla chiusura del file.

    L’hardware sottoposto a hotplug è un problema completamente diverso e non ci si deve aspettare che il programma rimanga in vita a lungo se gli inode o i metadati su disco non sono stati modificati affatto .

    Sotto / proc / directory troverai un elenco di tutti i processi attualmente attivi, basta trovare il tuo PID e tutti i dati relativi sono lì. Una informazione di sottofondo è 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 link sarà morto e sarà imansible aggiornare questo handle, il processo deve chiudersi e riaprilo (anche con riconnessione)

    Questo codice può leggere lo stato corrente del collegamento del PID

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

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

    code.c:

     #include  #include  #include  #include  #include  #include  #include  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 

    Usa gcc code.c per produrre a.out . Esegui ./a.out . Quando vedi il seguente risultato:

     line: 1234 

    Utilizzare i rm data per cancellare i data . Ma ./a.out continuerà a funzionare senza errori e produrrà il seguente output completo:

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

    Ho fatto l'esperimento su Ubuntu 16.04.3.