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

107

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

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

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

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

Maus
источник

Ответы:

159

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

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

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

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

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


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

MarkR
источник
Я полагаю, что ваш второй пункт применим в равной степени, если каталог, содержащий файл, удален. Это так?
Дрю Ноукс
2
Меня интересует одно: если вы используете команду cp для перезаписи файла, это первый случай или второй случай?
xuhdev
1
" Файл действительно не будет удален, пока не будет закрыт последний дескриптор. " Интересно. спасибо
Geremia
8

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

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

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

Ана Беттс
источник
5

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

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

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

Карл Смотрич
источник
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;
}
Кангер
источник
1
Какой смысл if(!fcntl(fd, F_GETFL)) {проверять? Я думаю, ты ищешь EBADFтам. (Вы также, вероятно, забыли инициализировать errnoзначение 0).
woky
Это не для меня. Я попытался использовать этот подход с open(O_WRONLY|O_APPEND)- st_nlink всегда оставаться> = 1, пока мой дескриптор открыт.
imbearr
2

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

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

Игнасио Васкес-Абрамс
источник
2

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

Цзинго Яо
источник
1

В каталоге / 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;
}
Дуглас Л
источник