web-dev-qa-db-fra.com

Qu'arrive-t-il à un descripteur de fichier ouvert sous Linux si le fichier pointé est déplacé, supprimez

Qu'advient-il d'un descripteur de fichier ouvert sous Linux si le fichier pointé obtient entre-temps:

  • Déplacé -> Le descripteur de fichier reste-t-il valide?
  • Supprimé -> Cela conduit-il à un EBADF, indiquant un descripteur de fichier non valide?
  • Remplacé par un nouveau fichier -> Le fichier gère-t-il le fait de pointer vers ce nouveau fichier?
  • Remplacé par un lien dur vers un nouveau fichier -> Mon fichier gère-t-il "suivre" ce lien?
  • Remplacé par un lien logiciel vers un nouveau fichier -> Mon descripteur de fichier at-il atteint ce fichier de lien logiciel maintenant?

Pourquoi je pose de telles questions: j'utilise du matériel branché à chaud (comme des périphériques USB, etc.). Il peut arriver que le périphérique (ainsi que son fichier/dev /) soit remis en place par l'utilisateur ou un autre Gremlin.

Quelle est la meilleure pratique pour gérer cela?

92
Maus

Si le fichier est déplacé (dans le même système de fichiers) ou renommé, le descripteur de fichier reste ouvert et peut toujours être utilisé pour lire et écrire le fichier.

Si le fichier est supprimé, le descripteur de fichier reste ouvert et peut toujours être utilisé (ce n'est pas ce que certaines personnes attendent). Le fichier ne sera vraiment supprimé que lorsque la dernière poignée sera fermée.

Si le fichier est remplacé par un nouveau fichier, cela dépend exactement comment. Si le contenu du fichier est écrasé, le descripteur de fichier sera toujours valide et accédera au nouveau contenu. Si le fichier existant n'est pas lié et qu'un nouveau est créé avec le même nom ou, si un nouveau fichier est déplacé vers le fichier existant à l'aide de rename(), c'est la même chose que la suppression (voir ci-dessus) - autrement dit, le Le descripteur de fichier continuera de faire référence à la version originale du fichier.

En général, une fois que le fichier est ouvert, le fichier est ouvert et personne ne modifiant la structure du répertoire ne peut changer cela - ils peuvent déplacer, renommer le fichier ou mettre quelque chose d'autre à sa place, il reste simplement ouvert.

Sous Unix, il n'y a pas de suppression, seulement unlink(), ce qui est logique car il ne supprime pas nécessairement le fichier - supprime simplement le lien du répertoire.


Si, en revanche, le périphérique sous-jacent disparaît (par exemple, débranchement USB), le descripteur de fichier ne sera plus valide et risque de donner des E/S sur toute opération. Vous devez cependant le fermer. Cela va être vrai même si l'appareil est rebranché, car il n'est pas judicieux de garder un fichier ouvert dans ce cas.

133
MarkR

Les poignées de fichier pointent vers un inode et non vers un chemin, donc la plupart de vos scénarios fonctionnent toujours comme vous le supposez, car la poignée pointe toujours vers le fichier.

Plus précisément, avec le scénario de suppression - la fonction est appelée "dissocier" pour une raison, elle détruit un "lien" entre un nom de fichier (une dentisterie) et un fichier. Lorsque vous ouvrez un fichier, puis le dissociez, le fichier existe toujours jusqu'à ce que son nombre de références atteigne zéro, c'est-à-dire lorsque vous fermez le handle.

Edit: Dans le cas du matériel, vous avez ouvert un handle vers un nœud de périphérique spécifique, si vous débranchez le périphérique, le noyau échouera à tous les accès, même si le périphérique revient. Vous devrez fermer l'appareil et le rouvrir.

7
Ana Betts

Je ne suis pas sûr des autres opérations, mais en ce qui concerne la suppression: la suppression n'a tout simplement pas lieu (physiquement, c'est-à-dire dans le système de fichiers) jusqu'à ce que la dernière poignée ouverte du fichier soit fermée. Ainsi, il ne devrait pas être possible de supprimer un fichier sous votre application.

Quelques applications (qui ne me viennent pas à l'esprit) s'appuient sur ce comportement, en créant, ouvrant et supprimant immédiatement des fichiers, qui vivent ensuite exactement aussi longtemps que l'application - permettant aux autres applications de connaître le cycle de vie de la première application sans avoir besoin de regardez les cartes de processus et autres.

Il est possible que des considérations similaires s'appliquent aux autres éléments.

4
Carl Smotricz

si vous voulez vérifier si le gestionnaire de fichiers (descripteur de fichier) est correct, vous pouvez appeler cette fonction.

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

Les informations en mémoire d'un fichier supprimé (tous les exemples que vous donnez sont des instances d'un fichier supprimé) ainsi que les inodes sur le disque restent en vigueur jusqu'à la fermeture du fichier.

Le matériel connecté à chaud est un problème complètement différent, et vous ne devez pas vous attendre à ce que votre programme reste en vie longtemps si les inodes ou les métadonnées sur le disque ont changé pas du tout.

2

L'expérience suivante montre que la réponse de MarkR est correcte.

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

les données:

1234
1234
1234
1234
1234

Utilisation gcc code.c produire a.out. Courir ./a.out. Lorsque vous voyez la sortie suivante:

line: 1234

Utilisation rm data pour supprimer data. Mais ./a.out continuera de fonctionner sans erreur et produira la sortie entière suivante:

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

J'ai fait l'expérience sur Ubuntu 16.04.3.

2
Jingguo Yao

Sous/proc/directory, vous trouverez une liste de tous les processus actuellement actifs, il vous suffit de trouver votre PID et toutes les données le concernant. Une info intéressante est le dossier fd /, vous trouverez tous les gestionnaires de fichiers actuellement ouverts par le processus.

Finalement, vous trouverez un lien symbolique vers votre appareil (sous/dev/ou même/proc/bus/usb /), si l'appareil se bloque, le lien sera mort et il sera impossible de rafraîchir cette poignée, le processus doit se fermer et l'ouvrir à nouveau (même avec reconnexion)

Ce code peut lire l'état actuel du lien de votre 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;
}

Ce code final est simple, vous pouvez jouer avec la fonction 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;
}
1
Douglas L