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

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

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

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

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

Ответы [ 7 ]

133 голосов
/ 09 января 2010

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

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

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

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

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


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

7 голосов
/ 08 января 2010

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

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

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

4 голосов
/ 08 января 2010

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

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

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

3 голосов
/ 05 февраля 2015

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

/**
 * 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;
}
2 голосов
/ 21 мая 2018

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

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

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

1 голос
/ 08 января 2010

В каталоге / 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]='\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;
}
...