What happens to an open file handle on Linux if the pointed file gets moved or deleted

What happens to an open file handle on Linux if the pointed file meanwhile gets:

  • Moved away -> Does the file handle stay valid?
  • Deleted -> Does this lead to an EBADF, indicating an invalid file handle?
  • Replaced by a new file -> Does the file handle pointing to this new file?
  • Replaced by a hard link to a new file -> Does my file handle "follow" this link?
  • Replaced by a soft link to a new file -> Does my file handle hit this soft link file now?

Why I'm asking such questions: I'm using hot-plugged hardware (such as USB devices etc.). It can happen, that the device (and also its /dev/file) gets reattached by the user or another Gremlin.

What's the best practice dealing with this?

57814 次浏览

The in-memory information of a deleted file (all the examples you give are instances of a deleted file) as well as the inodes on-disk remain in existence until the file is closed.

Hardware being hotplugged is a completely different issue, and you should not expect your program to stay alive long if the on-disk inodes or metadata have changed at all.

File handles point to an inode not to a path, so most of your scenarios still work as you assume, since the handle still points to the file.

Specifically, with the delete scenario - the function is called "unlink" for a reason, it destroys a "link" between a filename (a dentry) and a file. When you open a file, then unlink it, the file actually still exists until its reference count goes to zero, which is when you close the handle.

Edit: In the case of hardware, you have opened a handle to a specific device node, if you unplug the device, the kernel will fail all accesses to it, even if the device comes back. You will have to close the device and reopen it.

I'm not sure about the other operations, but as for deletion: Deletion simply doesn't take place (physically, i.e. in the file system) until the last open handle to the file is closed. Thus it should not be possible to delete a file out from under your application.

A few apps (that don't come to mind) rely on this behavior, by creating, opening and immediately deleting files, which then live exactly as long as the application - allowing other applications to be aware of the first app's lifecycle without needing to look at process maps and such.

It's possible similar considerations apply to the other stuff.

Under /proc/ directory you will find a list of every process currently active, just find your PID and all data regarding is there. An interresting info is the folder fd/, you will find all file handlers currently opened by the process.

Eventually you will find a symbolic link to your device (under /dev/ or even /proc/bus/usb/), if the device hangs the link will be dead and it will be impossible to refresh this handle, the process must close and open it again (even with reconnection)

This code can read your PID's link current status

#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;
}

This final code is simple, you can play with linkat function.

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;
}

If the file is moved (in the same filesystem) or renamed, then the file handle remains open and can still be used to read and write the file.

If the file is deleted, the file handle remains open and can still be used (This is not what some people expect). The file will not really be deleted until the last handle is closed.

If the file is replaced by a new file, it depends exactly how. If the file's contents are overwritten, the file handle will still be valid and access the new content. If the existing file is unlinked and a new one created with the same name or, if a new file is moved onto the existing file using rename(), it's the same as deletion (see above) - that is, the file handle will continue to refer to the original version of the file.

In general, once the file is open, the file is open, and nobody changing the directory structure can change that - they can move, rename the file, or put something else in its place, it simply remains open.

In Unix there is no delete, only unlink(), which makes sense as it doesn't necessarily delete the file - just removes the link from the directory.


If on the other hand the underlying device disappears (e.g. USB unplug) then the file handle won't be valid any more and is likely to give IO/error on any operation. You still have to close it though. This is going to be true even if the device is plugged back in, as it's not sensible to keep a file open in this case.

if you want to check if the file handler(file descriptor) is okay, you can call this function.

/**
* 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;
}

The following experiment shows that MarkR's answer is correct.

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;
}

data:

1234
1234
1234
1234
1234

Use gcc code.c to produce a.out. Run ./a.out. When you see the following output:

line: 1234

Use rm data to delete data. But ./a.out will continue to run without errors and produce the following whole output:

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

I have done the experiment on Ubuntu 16.04.3.