Подтвердить что ты не робот

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

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

  • Удалено → Дескриптор файла остается действительным?
  • Удалено → Приводит ли это к EBADF, указывая неверный дескриптор файла?
  • Заменяется новым файлом → Обрабатывает ли файл указатель на этот новый файл?
  • Заменено жесткой ссылкой на новый файл → Движок моего файла "следует" этой ссылке?
  • Заменен мягкой ссылкой на новый файл → Достиг ли теперь мой дескриптор файла этот файл мягкой ссылки?

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

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

4b9b3361

Ответ 1

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

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

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

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

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


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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

данные:

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.

Ответ 7

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

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

Этот код может читать текущее состояние вашей ПИД-связи

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

Этот окончательный код прост, вы можете играть с функцией 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;
}