如果指向的文件被移动或删除,Linux上的打开文件句柄会怎样?


107

如果指向文件同时获得,Linux上的打开文件句柄会发生什么:

  • 移开->文件句柄保持有效吗?
  • 删除->这是否导致EBADF,指示无效的文件句柄?
  • 替换为新文件->该文件是否处理指向该新文件的操作?
  • 由指向新文件的硬链接代替->我的文件可以“跟随”该链接吗?
  • 由指向新文件的软链接代替->我的文件句柄现在是否命中了该软链接文件?

为什么问这样的问题:我正在使用热插拔硬件(例如USB设备等)。可能会发生设备(以及它的/ dev / file)被用户或另一个Gremlin重新连接的情况。

处理此问题的最佳做法是什么?

Answers:


159

如果文件已移动(在同一文件系统中)或已重命名,则文件句柄将保持打开状态,并且仍可用于读取和写入文件。

如果删除了文件,则文件句柄将保持打开状态,并且仍然可以使用(这不是某些人所期望的)。在关闭最后一个句柄之前,不会真正删除该文件。

如果文件被新文件替换,则取决于具体方式。如果文件的内容被覆盖,则文件句柄将仍然有效并访问新内容。如果现有文件被取消链接,并且使用相同的名称创建了一个新文件,或者如果使用将新文件移动到了现有文件rename(),则该文件与删除操作相同(请参见上文)-也就是说,文件句柄将继续引用文件的原始版本。

通常,一旦打开文件,即打开文件,并且没有人更改目录结构就可以更改它-他们可以移动,重命名文件或在其位置放置其他内容,只是保持打开状态。

在Unix中,只有delete,unlink()这是有道理的,因为它不一定要删除文件-只是从目录中删除链接。


另一方面,如果基础设备消失(例如USB拔出),则文件句柄将不再有效,并且很可能在任何操作中产生IO /错误。您仍然必须关闭它。即使插回设备,也是如此,因为在这种情况下保持文件打开不明智。


我想如果删除文件的包含目录,第二点同样适用。是这样吗?
Drew Noakes 2014年

2
我对一件事感兴趣:如果您使用cp命令覆盖文件,是第一种情况还是第二种情况?
xuhdev 2014年

1
直到关闭最后一个句柄,该文件才真正删除。 ”有趣的是。谢谢
Geremia '18

8

文件句柄指向索引节点而不是路径,因此大多数情况仍然可以像您假设的那样工作,因为句柄仍指向文件。

具体来说,在删除方案中-该函数由于某种原因被称为“取消链接”,它破坏了文件名(牙科)和文件之间的“链接”。当您打开一个文件,然后取消链接时,该文件实际上仍然存在,直到其引用计数变为零为止,即关闭句柄时。

编辑:对于硬件,您已经打开了特定设备节点的句柄,如果您拔出设备的电源,即使设备返回,内核也将无法对其进行所有访问。您将必须关闭设备并重新打开它。


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

删除文件的内存信息(您提供的所有示例都是删除文件的实例)以及磁盘上的inode一直存在,直到关闭文件为止。

硬件被热插拔是完全不同的问题,如果磁盘上的inode或元数据完全改变了则您不应该期望程序会长时间存活。


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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.