In this tutorial, we’ll see how the system behaves when we delete, move, or replace a file that has open file handles.
First of all, we’ll briefly discuss files and inodes. Then, we’ll go through the different scenarios and see what happens in each of them.
2. Understanding Files and Inodes
When we work on a Linux file system, it uses inodes to store information about the files.
When we list the files in a folder, we see links to inodes. An inode can have more than one link, and those can be symbolic or hard links.
We can use stat on a file and see which inode it refers to:
$ touch inode_example $ stat inode_example File: inode_example Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd03h/64771d Inode: 20448632 Links: 1
This file refers to inode 20448632, and stat also says it has one link. Let’s add a new hard link and re-run stat on inode_example:
$ ln inode_example hardlink_inode_example $ stat inode_example File: inode_example Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd03h/64771d Inode: 20448632 Links: 2
We can see that the links value is incremented by one. Now, we can use stat on hardlink_inode_example, too:
$ stat hardlink_inode_example File: hardlink_inode_example Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd03h/64771d Inode: 20448632 Links: 2
Notice that the hard link also points to the same inode 20448632. Both files, hardlink_inode_example and inode_example, are hard links to the same inode.
Unlike hard links, symbolic links have their own inode numbers.
With all this in mind, we can think of a file name as an alias for an inode, and we can have more than one file linked to the same inode.
3. Deleting the File
When we open a file, we get a file descriptor pointing to it, also called file handle. We can use the opened file handle of a deleted file. We can write to and read from it as if the file exists. The file name won’t be visible in the file system, but our file handle will point to the inode, which still exists.
Let’s write a script called remove_opened_file.sh to test this idea:
#!/bin/bash FILE="/tmp/remove_example" ( sleep 1 echo "Before rm." >&4 rm "$FILE" if [ ! -e "$FILE" ]; then echo "The file $FILE was removed." fi echo "After rm." >&4 ) 4>"$FILE" & ( sleep 2 echo "This is the content in file handle 4:" cat <&4 ) 4<"$FILE"
In this script, we launch two sub-shells, and we use redirections to create and open /tmp/remove_example as file handle 4 in both sub-shells. The system will close the file handles when the sub-shell terminates.
Inside the first sub-shell, we start by waiting one second. We do this to be sure the second sub-shell has started and opened the file. Then, we write two lines to the file descriptor 4, one before removing the file and another after that. We also test that the file doesn’t exist before writing the last line. We do this just for clarity.
The second sub-shell starts by waiting two seconds, so it keeps the file opened while the first sub-shell writes to it and removes it. After that, it prints the content of the file handle 4.
Let’s run it and see what happens:
$ ./remove_opened_file.sh The file /tmp/remove_example was removed. This is the content in file handle 4: Before rm. After rm.
As we see, the second sub-shell prints both lines even when the file doesn’t exist.
This behavior is because when we remove a file, we’re actually unlinking the file name from the inode. The inode still exists, so we can write to and read from it if we keep the file open.
The system removes the inode when:
- There are no more hard links pointing to it
- There are no open file handles
This has another effect: If we remove a file but there are still open file handles pointing to it, the amount of free disk space won’t change.
4. Moving the File
There are two ways the system can move a file:
- Renaming the file
- Copying its content to the destination, then removing the source
Depending on the situation, the system uses one method or the other. For example, the system has to copy the file to the destination when we’re moving it to a different file system. On the other hand, the system can simply rename the file when working on the same file system.
4.1. The File Is Renamed
If the file is renamed, this doesn’t affect the open file handles. We can still write to and read from it. As the inode is the same, the content we write after the file is moved will be in the destination file.
Let’s write move_opened_file.sh to move an open file and see what happens:
#!/bin/bash FILE=/tmp/move_example FILE_NEW=/tmp/move_example.new ( echo "Before mv." >&4 mv "$FILE" "$FILE_NEW"; if [ ! -e "$FILE" -a -e "$FILE_NEW" ]; then echo "The file $FILE was moved to $FILE_NEW." fi echo "After mv." >&4 echo "This is the content in $FILE_NEW:" cat "$FILE_NEW" ) 4>"$FILE"
In this script, we use a sub-shell and redirections as we did in the previous section. We write to the open file handle before and after running the mv command. And finally, we use cat to print the new file’s content.
We can notice that the source and origin are in the same folder, /tmp, so the system can simply rename the file. Let’s see the result:
The file /tmp/move_example was moved to /tmp/move_example.new. This is the content in /tmp/move_example.new: Before mv. After mv.
As we can see from the output, the new file has both lines, even when we wrote both of them to the old file.
4.2. The File Is Copied
The behavior is different when the system copies the file to the destination. In this case, the content written to the source file after it was copied won’t be in the destination file. Also, we’ll lose this new content once we close the file handle. This is because the destination file will be a new inode. Our open file handle will point to the old inode, and it will be removed once the file is copied.
Now, let’s change $FILE_NEW in our move_opened_file.sh script, so the destination is in a different file system. We can change it to $FILE_NEW=~/move_by_copy_example.new, as usually /tmp and /home are in different file systems. Let’s see the result:
The file /tmp/move_example was moved to /home/baeldung/move_by_copy_example.new. This is the content in /home/baeldung/move_by_copy_example.new: Before mv.
As we expected, the new file only has the line before mv on it. We can still use the file handle, and it will behave as a deleted file.
5. Replacing the File
When the file is replaced, the situation is similar to deleting the file. If we have an open file handle and we replace it with another file, the system removes the original file. This means we can still use the file as long as we keep the file handle open.
We can write a script to test this behavior. Let’s call it replace_opened_file.sh:
#!/bin/bash FILE=/tmp/replace_example FILE_OLD=/tmp/replace_example.old echo "This is the old file." > $FILE_OLD ( sleep 1 echo "Before mv." >&4 mv "$FILE_OLD" "$FILE" echo "After mv." >&4 ) 4>"$FILE" & ( sleep 2 echo "This is the content in file handle 4:" cat <&4 echo "This is the content in $FILE:" cat $FILE ) 4<"$FILE"
In this script, we’ll first create an old file. Then we run two sub-shells opening a new file, as we did when we tested deleting the file. The first sub-shell will replace the new file with the old file. And finally, we print the content of the new file.
Let’s run replace_opened_file.sh:
$ ./replace_opened_file.sh This is the content in file handle 4: Before mv. After mv. This is the content in /tmp/replace_example: This is the old file.
As we see, as long as we have the file open, we can write to and read from it. However, we can see with the final line cat “$FILE” that when we re-opened the file, it had the old file’s content.
In this article, we discussed what happens when we delete, move, or replace a file that is still open.
We saw that we could remove or replace a file, and we can still use the open file handle as if it still exists, as long as we keep the file open.
On the other hand, we saw that we could move the file inside the same file system, and the open file handles will point to the new file. However, when we move the file to another file system, our file handle will point to the old file.