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

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

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

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

какая лучшая практика имеет дело с этим?

7 ответов


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

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

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

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

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


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


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

в частности, со сценарием удаления-функция называется" 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;
}

В конце концов вы найдете символическую ссылку на свое устройство (под /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. Но!--6--> будет продолжать работать без ошибок и производим весь следующий результат:

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

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