Was passiert mit einem geöffneten Dateihandle unter Linux, wenn die angegebene Datei verschoben oder gelöscht wird?


108

Was passiert mit einem offenen Dateihandle unter Linux, wenn die spitze Datei inzwischen Folgendes erhält:

  • Verschoben -> Bleibt das Dateihandle gültig?
  • Gelöscht -> Führt dies zu einer EBADF, die auf ein ungültiges Dateihandle hinweist?
  • Ersetzt durch eine neue Datei -> Zeigt das Dateihandle auf diese neue Datei?
  • Ersetzt durch einen festen Link zu einer neuen Datei -> Verfolgt meine Datei diesen Link?
  • Ersetzt durch einen Softlink zu einer neuen Datei -> Trifft mein Dateihandle jetzt auf diese Softlinkdatei?

Warum ich solche Fragen stelle: Ich verwende Hot-Plug-Hardware (wie USB-Geräte usw.). Es kann vorkommen, dass das Gerät (und auch seine / dev / -Datei) vom Benutzer oder einem anderen Gremlin erneut angeschlossen wird.

Was ist die beste Vorgehensweise, um damit umzugehen?

Antworten:


160

Wenn die Datei verschoben (im selben Dateisystem) oder umbenannt wird, bleibt das Dateihandle geöffnet und kann weiterhin zum Lesen und Schreiben der Datei verwendet werden.

Wenn die Datei gelöscht wird, bleibt das Dateihandle geöffnet und kann weiterhin verwendet werden (dies ist nicht das, was manche Leute erwarten). Die Datei wird erst wirklich gelöscht, wenn das letzte Handle geschlossen ist.

Wenn die Datei durch eine neue Datei ersetzt wird, hängt es genau davon ab, wie. Wenn der Inhalt der Datei überschrieben wird, bleibt das Dateihandle weiterhin gültig und greift auf den neuen Inhalt zu. Wenn die vorhandene Datei nicht verknüpft ist und eine neue Datei mit demselben Namen erstellt wurde oder wenn eine neue Datei mit in die vorhandene Datei verschoben wird rename(), entspricht dies dem Löschen (siehe oben). Das heißt, das Dateihandle verweist weiterhin auf die Originalversion der Datei.

Sobald die Datei geöffnet ist, ist sie im Allgemeinen geöffnet, und niemand, der die Verzeichnisstruktur ändert, kann dies ändern. Er kann die Datei verschieben, umbenennen oder etwas anderes an ihre Stelle setzen. Sie bleibt einfach geöffnet.

Unter Unix gibt es nur kein Löschen, unlink()was sinnvoll ist, da es nicht unbedingt die Datei löscht - sondern nur den Link aus dem Verzeichnis entfernt.


Wenn andererseits das zugrunde liegende Gerät verschwindet (z. B. USB-Stecker), ist das Dateihandle nicht mehr gültig und gibt bei jeder Operation wahrscheinlich E / A / Fehler aus. Sie müssen es jedoch noch schließen. Dies gilt auch dann, wenn das Gerät wieder angeschlossen ist, da es in diesem Fall nicht sinnvoll ist, eine Datei offen zu halten.


Ich nehme an, dass Ihr zweiter Punkt gleichermaßen gilt, wenn ein enthaltenes Verzeichnis der Datei gelöscht wird. Ist das so?
Drew Noakes

2
Ich interessiere mich für eine Sache: Wenn Sie eine Datei mit dem Befehl cp überschreiben, ist dies der erste oder der zweite Fall?
Xuhdev

1
" Die Datei wird erst wirklich gelöscht, wenn das letzte Handle geschlossen ist. " Interessant. danke
Geremia

8

Dateihandles verweisen auf einen Inode und nicht auf einen Pfad. Daher funktionieren die meisten Ihrer Szenarien weiterhin wie angenommen, da das Handle weiterhin auf die Datei verweist.

Insbesondere beim Löschszenario wird die Funktion aus einem bestimmten Grund als "Verknüpfung aufheben" bezeichnet und zerstört eine "Verknüpfung" zwischen einem Dateinamen (einem Eintrag) und einer Datei. Wenn Sie eine Datei öffnen und dann die Verknüpfung aufheben, ist die Datei tatsächlich noch vorhanden, bis ihre Referenzanzahl auf Null geht. Dann schließen Sie das Handle.

Bearbeiten: Bei Hardware haben Sie ein Handle für einen bestimmten Geräteknoten geöffnet. Wenn Sie das Gerät vom Computer trennen, schlägt der Kernel alle Zugriffe darauf fehl, auch wenn das Gerät zurückkommt. Sie müssen das Gerät schließen und erneut öffnen.


5

Ich bin mir bei den anderen Vorgängen nicht sicher, aber was das Löschen betrifft: Das Löschen erfolgt einfach nicht (physisch, dh im Dateisystem), bis das letzte geöffnete Handle für die Datei geschlossen wird. Daher sollte es nicht möglich sein, eine Datei unter Ihrer Anwendung zu löschen.

Einige Apps (die mir nicht in den Sinn kommen) stützen sich auf dieses Verhalten, indem sie Dateien erstellen, öffnen und sofort löschen, die dann genau so lange wie die Anwendung gültig sind. Dadurch können andere Anwendungen den Lebenszyklus der ersten App kennen, ohne dass dies erforderlich ist Schauen Sie sich Prozesslandkarten und dergleichen an.

Es ist möglich, dass ähnliche Überlegungen für die anderen Dinge gelten.


4

Wenn Sie überprüfen möchten, ob der Dateihandler (Dateideskriptor) in Ordnung ist, können Sie diese Funktion aufrufen.

/**
 * 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
Was ist der Sinn der if(!fcntl(fd, F_GETFL)) {Prüfung? Ich denke du suchst EBADFdort. (Sie haben wahrscheinlich auch vergessen, errnoauf 0 zu initialisieren ).
woky

Das funktioniert bei mir nicht. Ich habe versucht, diesen Ansatz zu verwenden, wobei open(O_WRONLY|O_APPEND)- st_nlink immer> = 1 bleibt, während mein Deskriptor geöffnet ist.
Imbearr

2

Die speicherinternen Informationen einer gelöschten Datei (alle Beispiele, die Sie angeben, sind Instanzen einer gelöschten Datei) sowie die Inodes auf der Festplatte bleiben bestehen, bis die Datei geschlossen wird.

Hardware Hotplug wird , ist eine ganz andere Frage, und Sie sollten nicht erwarten , dass Ihr Programm so lange am Leben bleiben , wenn der On-Disk - I - Nodes oder Metadaten geändert hat überhaupt .


2

Das folgende Experiment zeigt, dass die Antwort von MarkR korrekt ist.

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

Daten:

1234
1234
1234
1234
1234

Verwenden Sie gcc code.czu produzieren a.out. Ausführen ./a.out. Wenn Sie die folgende Ausgabe sehen:

line: 1234

Verwenden Sie rm datazu löschen data. Aber ./a.outwird auch weiterhin ohne Fehler und erzeugen die folgende gesamte Ausgabe auszuführen:

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

Ich habe das Experiment unter Ubuntu 16.04.3 durchgeführt.


1

Unter / proc / directory finden Sie eine Liste aller aktuell aktiven Prozesse. Finden Sie einfach Ihre PID und alle Daten dazu sind vorhanden. Eine interessante Information ist der Ordner fd /. Dort finden Sie alle Dateihandler, die derzeit vom Prozess geöffnet werden.

Schließlich finden Sie einen symbolischen Link zu Ihrem Gerät (unter / dev / oder sogar / proc / bus / usb /). Wenn das Gerät hängt, ist der Link tot und es ist unmöglich, dieses Handle zu aktualisieren. Der Prozess muss geschlossen werden und öffne es wieder (auch bei erneuter Verbindung)

Dieser Code kann den aktuellen Linkstatus Ihrer PID lesen

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

Dieser endgültige Code ist einfach, Sie können mit der Linkat-Funktion spielen.

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;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.