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

107

Qu'arrive-t-il à 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 ce nouveau fichier?
  • Remplacé par un lien en 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 atteint-il ce fichier de lien logiciel maintenant?

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

Quelle est la meilleure pratique pour y remédier?

Maus
la source

Réponses:

159

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 à quoi certaines personnes s'attendent). Le fichier ne sera pas vraiment supprimé tant que la dernière poignée ne sera pas 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 est dissocié et un nouveau 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) - c'est-à-dire que le descripteur de fichier continuera à 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 peut changer la structure des répertoires - ils peuvent déplacer, renommer le fichier ou mettre autre chose à sa place, il reste simplement ouvert.

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


Si d'un autre côté le périphérique sous-jacent disparaît (par ex. Débranchement USB) alors le descripteur de fichier ne sera plus valide et est susceptible de donner IO / erreur sur toute opération. Vous devez encore le fermer. Cela sera vrai même si l'appareil est rebranché, car il n'est pas judicieux de garder un fichier ouvert dans ce cas.

MarkR
la source
Je suppose que votre deuxième point s'applique également si un répertoire contenant du fichier est supprimé. Est-ce vrai?
Drew Noakes
2
Je suis intéressé par une chose: si vous utilisez la commande cp pour écraser un fichier, est-ce le premier cas ou le deuxième cas?
xuhdev
1
" Le fichier ne sera pas vraiment supprimé tant que la dernière poignée ne sera pas fermée. " Intéressant. merci
Geremia
8

Les descripteurs 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 le descripteur 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 (un dentry) et un fichier. Lorsque vous ouvrez un fichier, puis dissociez-le, 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.

Ana Betts
la source
5

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) tant que le dernier descripteur ouvert du fichier n'est pas fermé. Ainsi, il ne devrait pas être possible de supprimer un fichier de sous votre application.

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

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

Carl Smotricz
la source
4

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;
}
kangear
la source
1
Quel est le point de if(!fcntl(fd, F_GETFL)) {contrôle? Je suppose que vous cherchez EBADFlà-bas. (Vous avez probablement également oublié d'initialiser errnoà 0).
woky
Cela ne fonctionne pas pour moi. J'ai essayé d'utiliser cette approche avec open(O_WRONLY|O_APPEND)- st_nlink reste toujours> = 1 pendant que mon descripteur est ouvert.
imbearr
2

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'à ce que le fichier soit fermé.

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

Ignacio Vazquez-Abrams
la source
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

Utilisez gcc code.cpour produire a.out. Courez ./a.out. Lorsque vous voyez la sortie suivante:

line: 1234

Utilisez rm datapour supprimer data. Mais ./a.outcontinuera à fonctionner sans erreurs et produira la sortie complète suivante:

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

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

Jingguo Yao
la source
1

Sous le répertoire / proc /, vous trouverez une liste de tous les processus actuellement actifs, trouvez simplement votre PID et toutes les données s'y rapportant. Une information 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 d'actualiser cette poignée, le processus doit se fermer et ouvrez-le à 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;
}
Douglas L
la source