Что происходит с дескриптором открытого файла в Linux, если указанный файл перемещается, удалите
Что происходит с дескриптором открытого файла в Linux, если указанный файл тем временем получает:
- Удалено -> Дескриптор файла остается действительным?
- Удалено -> Приводит ли это к EBADF, указывая на неверный дескриптор файла?
- Заменяется новым файлом -> Обрабатывает ли файл указатель на этот новый файл?
- Заменено жесткой ссылкой на новый файл -> Движок моего файла "следует" этой ссылке?
- Заменяется программной ссылкой на новый файл -> Достиг ли теперь мой дескриптор файла этот файл программной ссылки?
Почему я задаю такие вопросы: я использую аппаратное обеспечение с горячей заменой (например, USB-устройства и т. Д.). Может случиться так, что устройство (а также его /dev/file) будет подключено пользователем или другим Gremlin.
Какова лучшая практика борьбы с этим?
7 ответов
Если файл перемещен (в той же файловой системе) или переименован, то дескриптор файла остается открытым и все еще может использоваться для чтения и записи файла.
Если файл удален, дескриптор файла остается открытым и все еще может использоваться (это не то, что ожидают некоторые люди). Файл на самом деле не будет удален, пока последний дескриптор не будет закрыт.
Если файл заменен новым, это зависит от того, как именно. Если содержимое файла будет перезаписано, дескриптор файла останется действительным и получит доступ к новому содержимому. Если существующий файл не связан и новый файл создан с тем же именем или, если новый файл перемещен в существующий файл с помощью rename()
, это то же самое, что удаление (см. выше) - то есть дескриптор файла будет продолжать ссылаться на исходную версию файла.
В общем, как только файл открыт, файл открыт, и никто не может изменить его структуру каталогов - он может перемещать, переименовывать файл или помещать что-то еще на его место, он просто остается открытым.
В Unix нет удаления, только unlink()
, что имеет смысл, поскольку не обязательно удаляет файл - просто удаляет ссылку из каталога.
Если, с другой стороны, основное устройство исчезнет (например, отключение USB), то дескриптор файла больше не будет действительным и, скорее всего, выдаст IO/ ошибку при любой операции. Вы все еще должны закрыть это все же. Это будет верно, даже если устройство подключено обратно, так как в этом случае не имеет смысла держать файл открытым.
Файловые дескрипторы указывают на индекс, а не на путь, поэтому большинство ваших сценариев по-прежнему работают так, как вы предполагаете, поскольку дескриптор по-прежнему указывает на файл.
В частности, в сценарии удаления - функция называется "unlink" по причине, она уничтожает "связь" между именем файла (dentry) и файлом. Когда вы открываете файл, а затем отсоединяете его, файл фактически все еще существует, пока его счетчик ссылок не станет равным нулю, то есть когда вы закроете дескриптор.
Редактировать: В случае аппаратного обеспечения вы открыли дескриптор для определенного узла устройства, и если вы отключите устройство от сети, ядро не выполнит все обращения к нему, даже если устройство вернется. Вам придется закрыть устройство и снова открыть его.
Я не уверен насчет других операций, но что касается удаления: удаление просто не происходит (физически, то есть в файловой системе), пока не будет закрыт последний открытый дескриптор файла. Таким образом, не должно быть возможности удалить файл из-под вашего приложения.
Несколько приложений (которые не приходят в голову) полагаются на это поведение, создавая, открывая и немедленно удаляя файлы, которые затем живут ровно столько же времени, сколько и приложение, что позволяет другим приложениям знать жизненный цикл первого приложения без необходимости посмотрите на карты процессов и тому подобное.
Возможно, аналогичные соображения применимы и к другим вещам.
Если вы хотите проверить, в порядке ли обработчик файла (дескриптор файла), вы можете вызвать эту функцию.
/**
* 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;
}
Информация удаленного файла в памяти (все примеры, которые вы приводите, является экземплярами удаленного файла), а также находящиеся на диске inode остаются до тех пор, пока файл не будет закрыт.
Аппаратное горячее подключение - это совсем другая проблема, и вы не должны ожидать, что ваша программа будет долго работать, если inode in-disk или метаданные вообще изменились.
Следующий эксперимент показывает, что ответ 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.
В / 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;
}