it-swarm.com.ru

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

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

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

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

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

92
Maus

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

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

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

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

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


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

133
MarkR

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

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

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

7
Paul Betts

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

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

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

4
Carl Smotricz

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

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

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

Аппаратное горячее подключение - это совсем другая проблема, и вы не должны ожидать, что ваша программа долго будет жить, если inode in-disk или метаданные изменились вообще.

2
Ignacio Vazquez-Abrams

Следующий эксперимент показывает, что ответ 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.

2
Jingguo Yao

В/proc/directory вы найдете список всех процессов, которые в данный момент активны, просто найдите ваш 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]='\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;
}
1
Douglas L