Co dzieje się z dojściem do otwartego pliku w systemie Linux, jeśli wskazany plik zostanie przeniesiony lub usunięty

107

Co dzieje się z dojściem do otwartego pliku w systemie Linux, jeśli w międzyczasie wskazany plik otrzyma:

  • Usunięto -> Czy uchwyt pliku pozostaje ważny?
  • Usunięte -> Czy prowadzi to do EBADF, wskazującego nieprawidłowy uchwyt pliku?
  • Zastąpiony przez nowy plik -> Czy plik wskazuje na ten nowy plik?
  • Zastąpiony przez twardy link do nowego pliku -> Czy mój plik obsługuje ten link?
  • Zastąpiony przez miękki link do nowego pliku -> Czy mój uchwyt pliku trafia teraz do tego miękkiego linku?

Dlaczego zadaję takie pytania: używam sprzętu podłączanego na gorąco (takiego jak urządzenia USB itp.). Może się zdarzyć, że urządzenie (a także jego / dev / plik) zostanie ponownie podłączone przez użytkownika lub innego Gremlina.

Jaka jest najlepsza praktyka radzenia sobie z tym?

mysz
źródło

Odpowiedzi:

159

Jeśli plik zostanie przeniesiony (w tym samym systemie plików) lub zmieniona jego nazwa, uchwyt pliku pozostanie otwarty i nadal może być używany do odczytu i zapisu pliku.

Jeśli plik zostanie usunięty, uchwyt pliku pozostaje otwarty i nadal można go używać (nie jest to oczekiwane przez niektórych). Plik nie zostanie tak naprawdę usunięty, dopóki ostatni uchwyt nie zostanie zamknięty.

Jeśli plik zostanie zastąpiony nowym plikiem, zależy to dokładnie w jaki sposób. Jeśli zawartość pliku zostanie nadpisana, uchwyt pliku będzie nadal ważny i będzie miał dostęp do nowej zawartości. Jeśli istniejący plik zostanie odłączony i utworzony nowy o tej samej nazwie lub, jeśli nowy plik zostanie przeniesiony do istniejącego pliku za pomocą rename(), jest to to samo, co usunięcie (patrz powyżej) - to znaczy uchwyt pliku będzie nadal odnosił się do oryginalna wersja pliku.

Ogólnie rzecz biorąc, kiedy plik jest otwarty, plik jest otwarty i nikt nie zmieniając struktury katalogów nie może tego zmienić - mogą przenieść, zmienić nazwę pliku lub umieścić coś innego w jego miejscu, po prostu pozostaje otwarty.

W Uniksie nie ma tylko usuwania, unlink()co ma sens, ponieważ niekoniecznie usuwa plik - po prostu usuwa link z katalogu.


Z drugiej strony, jeśli urządzenie bazowe zniknie (np. Odłączenie USB), wtedy uchwyt pliku nie będzie już poprawny i prawdopodobnie spowoduje IO / błąd przy dowolnej operacji. Jednak nadal musisz go zamknąć. Będzie to prawdą, nawet jeśli urządzenie zostanie ponownie podłączone, ponieważ w tym przypadku nie jest rozsądne pozostawienie otwartego pliku.

MarkR
źródło
Przypuszczam, że twój drugi punkt ma takie samo zastosowanie, jeśli katalog zawierający plik zostanie usunięty. Czy tak jest?
Drew Noakes
2
Interesuje mnie jedno: jeśli używasz polecenia cp do nadpisania pliku, czy jest to pierwszy przypadek, czy drugi przypadek?
xuhdev
1
Plik nie zostanie w rzeczywistości usunięty, dopóki ostatni uchwyt nie zostanie zamknięty ”. dzięki
Geremia
8

Uchwyty plików wskazują na i-węzeł, a nie na ścieżkę, więc większość scenariuszy nadal działa tak, jak zakładasz, ponieważ uchwyt nadal wskazuje na plik.

W szczególności w przypadku scenariusza usuwania - funkcja nie bez powodu nazywana jest „odłączaniem”, niszczy ona „łącze” między nazwą pliku (dentry) a plikiem. Po otwarciu pliku, a następnie odłączeniu go, plik faktycznie istnieje, dopóki jego liczba odwołań nie spadnie do zera, czyli po zamknięciu uchwytu.

Edycja: W przypadku sprzętu otworzyłeś uchwyt do określonego węzła urządzenia, jeśli odłączysz urządzenie, jądro nie powiedzie dostępu do niego, nawet jeśli urządzenie wróci. Będziesz musiał zamknąć urządzenie i ponownie je otworzyć.

Ana Betts
źródło
5

Nie jestem pewien co do innych operacji, ale jeśli chodzi o usuwanie: usuwanie po prostu nie ma miejsca (fizycznie, tj. W systemie plików), dopóki ostatnie otwarte dojście do pliku nie zostanie zamknięte. W związku z tym usunięcie pliku z aplikacji nie powinno być możliwe.

Kilka aplikacji (które nie przychodzą na myśl) polega na tym zachowaniu, tworząc, otwierając i natychmiast usuwając pliki, które następnie działają dokładnie tak długo, jak aplikacja - pozwalając innym aplikacjom być świadomym cyklu życia pierwszej aplikacji bez konieczności spójrz na mapy procesów i tym podobne.

Możliwe, że podobne uwagi mają zastosowanie do innych rzeczy.

Carl Smotricz
źródło
4

jeśli chcesz sprawdzić, czy program obsługi plików (deskryptor pliku) jest w porządku, możesz wywołać tę funkcję.

/**
 * 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
źródło
1
Jaki jest sens if(!fcntl(fd, F_GETFL)) {sprawdzania? Myślę, że szukasz EBADFtam. (Prawdopodobnie zapomniałeś również zainicjować errnona 0).
wokalny
To nie działa dla mnie. Próbowałem użyć tego podejścia z open(O_WRONLY|O_APPEND)- st_nlink zawsze pozostaje> = 1, gdy mój deskryptor jest otwarty.
imbearr
2

Informacje w pamięci o usuniętym pliku (wszystkie podane przykłady są wystąpieniami usuniętego pliku), a także i-węzły na dysku pozostają w pamięci do momentu zamknięcia pliku.

Podłączanie sprzętu podczas pracy to zupełnie inny problem i nie należy oczekiwać, że program będzie działał długo, jeśli i-węzły lub metadane na dysku w ogóle ulegną zmianie .

Ignacio Vazquez-Abrams
źródło
2

Poniższy eksperyment pokazuje, że odpowiedź MarkR jest poprawna.

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

dane:

1234
1234
1234
1234
1234

Użyj gcc code.cdo produkcji a.out. Biegnij ./a.out. Gdy zobaczysz następujący wynik:

line: 1234

Użyj, rm dataaby usunąć data. Ale ./a.outbędzie nadal działać bez błędów i generuje następujący wynik:

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

Zrobiłem eksperyment na Ubuntu 16.04.3.

Jingguo Yao
źródło
1

W katalogu / proc / znajdziesz listę wszystkich aktualnie aktywnych procesów, po prostu znajdź swój PID i wszystkie dotyczące go dane. Interesującą informacją jest folder fd /, znajdziesz wszystkie programy obsługi plików aktualnie otwarte przez proces.

W końcu znajdziesz symboliczne łącze do swojego urządzenia (pod / dev / lub nawet / proc / bus / usb /), jeśli urządzenie się zawiesi, łącze będzie martwe i nie będzie można odświeżyć tego uchwytu, proces musi zostać zamknięty i otwórz go ponownie (nawet po ponownym połączeniu)

Ten kod może odczytać aktualny status łącza 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;
}

Ten końcowy kod jest prosty, możesz bawić się funkcją 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.
źródło