Що відбувається з відкритою файловою ручкою в Linux, якщо загострений файл буде переміщений або видалений


107

Що станеться з відкритою файловою ручкою в Linux, якщо вказаний файл тим часом отримує:

  • Відсунуто -> Чи залишається обробка файлу дійсною?
  • Видалено -> Чи це призводить до EBADF, що вказує на недійсну обробку файлу?
  • Замінений новим файлом -> Чи обробляє файл вказівку на цей новий файл?
  • Замінено жорстким посиланням на новий файл -> Чи обробляє мій файл "перехід" за цим посиланням?
  • Замінений м'яким посиланням на новий файл -> Чи обробляє мій файл зараз цей файл м'якого посилання?

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

Яка найкраща практика боротьби з цим?

Відповіді:


159

Якщо файл переміщено (в тій же файловій системі) або перейменовано, то ручка файлу залишається відкритою і все ще може використовуватися для читання та запису файлу.

Якщо файл буде видалено, ручка файлу залишається відкритою і все ще може бути використана (деякі люди не очікують цього). Файл дійсно не буде видалений, поки не закриється остання ручка.

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

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

У Unix немає лише видалення, unlink()що має сенс, оскільки він не обов'язково видаляє файл - просто видаляє посилання з каталогу.


Якщо з іншого боку базовий пристрій зникає (наприклад, відключення USB), обробка файлів більше не буде дійсною і, ймовірно, дасть IO / помилку під час будь-якої операції. Ви все одно повинні закрити його. Це буде правдою навіть у тому випадку, якщо пристрій підключено назад, оскільки в цьому випадку файл не відкритий.


Я вважаю, що ваш другий пункт застосовується однаково, якщо видалений каталог файлу видалений. Невже це так?
Дрю Ноакс

2
Мене цікавить одне: якщо ви використовуєте команду cp для перезаписування файлу, це перший випадок чи другий випадок?
xuhdev

1
" Файл дійсно не буде видалений, поки не закриється остання ручка. " дякую
Геремія

8

Ручки файлів вказують на inode не на шлях, тому більшість сценаріїв все ще працюють, як ви припускаєте, оскільки ручка все ще вказує на файл.

Зокрема, зі сценарієм видалення - функція з причини називається "від’єднати", вона руйнує "посилання" між назвою файлу (зубний ряд) та файлом. Коли ви відкриваєте файл, а потім від'єднуєте його, файл насправді існує доти, доки його кількість відліку не перейде до нуля, тобто коли ви закриєте ручку.

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


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).
вокі

Для мене це не працює. Я намагався використовувати цей підхід за допомогою open(O_WRONLY|O_APPEND)- st_nlink завжди залишається> = 1, поки мій дескриптор відкритий.
imbearr

2

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

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


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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.