Что происходит с дескриптором открытого файла в Linux, если указанный файл перемещается, удалите


Что происходит с дескриптором открытого файла в Linux, если указанный файл тем временем получает:

  • отодвинул - > остается ли дескриптор файла действительным?
  • Deleted - > приводит ли это к EBADF, указывая на недопустимый дескриптор файла?
  • заменен новым файлом - > есть ли дескриптор файла, указывающий на этот новый файл?
  • заменена жесткой ссылкой на новый файл -> мой файл обрабатывает "следовать" этой ссылке?
  • заменена мягкой ссылкой на новый файл - >Мой файловый дескриптор попал в этот файл мягкой ссылки сейчас?

Почему я задаю такие вопросы: я использую горячее оборудование (например, USB-устройства и т. д.). Может случиться так, что устройство (а также его /dev/файл) будет повторно подключено пользователем или другим Gremlin.

какова лучшая практика борьбы с этим?

7 77

7 ответов:

Если файл перемещен (в той же файловой системе) или переименован, то дескриптор файла остается открытым и все еще может использоваться для чтения и записи файла.

Если файл удален, ручка файл остается открытым и все еще может быть использован (это не то, что некоторые люди ожидают). Файл не будет удален, пока не будет закрыт последний дескриптор.

Если файл заменен новым файлом, это зависит от того, как именно. Если содержимое файла перезаписано, дескриптор файла будет сохранен будьте действительны и получите доступ к новому контенту. Если существующий файл не связан и новый создан с тем же именем или, если новый файл перемещается в существующий файл с помощью rename(), Это то же самое, что удаление (см. выше) - то есть дескриптор файла будет продолжать ссылаться на оригинал версия файла.

В общем, как только файл открыт, файл открыт, и никто не может изменить структуру каталогов - они могут перемещать, переименовывать файл или помещать что-то иначе на своем месте он просто остается открытым.

в Unix нет удаления, только unlink(), что имеет смысл, поскольку он не обязательно удаляет файл - просто удаляет ссылку из каталога.


Если с другой стороны базовое устройство исчезает (например, USB unplug), то дескриптор файла больше не будет действителен и, вероятно, даст IO/error при любой операции. Тебе придется закрыть его. Это будет верно, даже если устройство подключено обратно в, поскольку в этом случае нецелесообразно держать файл открытым.

дескрипторы файлов указывают на индекс, а не на путь, поэтому большинство ваших сценариев все еще работают так, как вы предполагаете, поскольку дескриптор все еще указывает на файл.

в частности, при сценарии удаления-функция называется" unlink "по какой-то причине, она уничтожает" связь " между именем файла (dentry) и файлом. Когда вы открываете файл, а затем разблокируете его, файл фактически все еще существует, пока его счетчик ссылок не достигнет нуля, что происходит при закрытии ручка.

Edit: в случае аппаратного обеспечения вы открыли дескриптор для определенного узла устройства, если вы отключите устройство, ядро не сможет получить к нему доступ, даже если устройство вернется. Вам придется закрыть устройство и снова открыть его.

Я не уверен в других операциях, но что касается удаления: удаление просто не происходит (физически, т. е. в файловой системе), пока последний открытый дескриптор файла не будет закрыт. Таким образом, не должно быть возможно удалить файл из-под вашего приложения.

несколько приложений (которые не приходят на ум) полагаются на это поведение, создавая, открывая и немедленно удаляя файлы, которые затем живут ровно столько, сколько приложение-позволяя другим приложениям быть в курсе из жизненного цикла первого приложения без необходимости смотреть на карты процессов и тому подобное.

вполне возможно, подобные соображения применимы и к другим вещам.

информация в памяти удаленного файла (все примеры вы даете экземпляры удаленного файла), а также inodes на диске остаются в существовании, пока файл не будет закрыт.

аппаратное обеспечение hotplugged-это совершенно другая проблема, и вы не должны ожидать, что ваша программа останется в живых долго, если на диске изменились индексы или метаданные на всех.

Если вы хотите проверить, если обработчик файла(дескриптор файла) в порядке, вы можете вызвать эту функцию.

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

в каталоге /proc/ вы найдете список всех процессов, которые в данный момент активны, просто найдите свой PID и все данные, касающиеся этого. Интересной информацией является папка fd/, вы найдете все обработчики файлов, открытые в данный момент процессом.

В конце концов вы найдете символическую ссылку на ваше устройство (под /dev / или даже /proc / bus / usb/), если устройство зависает, ссылка будет мертва, и будет невозможно обновить этот дескриптор, процесс должен закрыть и открыть его снова (даже с переподключением)

этот код может прочитать текущее состояние ссылки вашего 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]='';
                        printf("%s -> %s\n", dir->d_name,hardfile);

                } else
                    printf("error when executing readlink() on %s\n",tempath);

            }
        }

        closedir(d);
    }
    return 0;
}

этот код прост, вы можете играть с функцией linkat.

int
open_dir(char * path)
{
  int fd;

  path = strdup(path);
  *strrchr(path, '/') = '';
  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;
}

следующий эксперимент показывает, что MarkR это является правильным.

код.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;
}

данные:

1234
1234
1234
1234
1234

использовать gcc code.c для производства a.out. Беги ./a.out. Когда вы видите следующий вывод:

line: 1234

использовать rm data удалить data. Но ./a.out будет продолжать работать без ошибок и производим весь следующий результат:

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

я провел эксперимент на Ubuntu 16.04.3.