The “\$Extend\$Deleted” directory

In the Linux world, a deleted file which is still open isn’t actually removed from a disk. Instead, it’s just unlinked from the directory structure. This is why a system call used to remove files is named “unlink”.

unlink() deletes a name from the filesystem. If that name was the last link to a file and no processes have the file open, the file is deleted and the space it was using is made available for reuse.

If the name was the last link to a file but any processes still have the file open, the file will remain in existence until the last file descriptor referring to it is closed.

(Source.)

The same behavior can be observed in other Unix-like operating systems.

But in Windows 10, similar behavior can be seen too!

If a file is opened in a way that allows to delete this file from another process (this can be achieved by specifying the FILE_SHARE_DELETE flag for the CreateFile function) and that process tries to delete it, the file is actually moved to the “\$Extend\$Deleted” directory on the same volume.

When the last handle to such a file is closed, it’s deleted as usual. When the file system is mounted, all existing files in the “\$Extend\$Deleted” directory (if any) are deleted (thus, a system crash won’t result in leftover files being stored forever).

The same logic also applies to “deleted” directories.

During usual operations of the operating system, such “deleted” files constantly appear and disappear on the system volume. Here is a listing from the “\$Extend\$Deleted” directory of a Windows 10 installation in a virtual machine (this is the output of the fls tool, each entry is marked as deleted, you can see both files and directories):

-/d * 21437-144-6: 000C0000000053BD1204FEEF
-/r * 21440-128-1: 000C0000000053BD1204FEEF/summary.txt
-/r * 21441-128-4: 000C0000000053BD1204FEEF/summary.json
-/r * 21442-128-4: 000C0000000053BD1204FEEF/scenario_data.xml
-/d * 21439-144-1: 000C0000000053BF535BA4C6
-/d * 21443-144-1: 000B0000000053C3193045B7
-/d * 21444-144-1: 000B0000000053C446D7B136
-/d * 21445-144-1: 000B0000000053C502CAB389
-/d * 21446-144-1: 000B0000000053C63BFD74DD
-/d * 21447-144-1: 000B0000000053C70AB5A416
-/d * 21448-144-1: 000B0000000053C81DACF52E
-/d * 21449-144-1: 000C0000000053C93E41B172
-/d * 21450-144-1: 000B0000000053CA3F1165AD
-/d * 21451-144-1: 000B0000000053CB557B3B43
-/r * 21493-128-4: 00150000000053F54ED9BE0B
-/d * 85145-144-1: 0015000000014C9976249331
-/r * 85149-128-1: 0015000000014C9976249331/container.dat

All file names in the “\$Extend\$Deleted” directory are munged (this doesn’t apply to files in subdirectories). The format of a munged file name is:

<a file reference number (of this file) as a HEX string><four random bytes as a HEX string)>

For example: “0015000000014C9976249331″. The file reference number is shown in bold (the file record number is 85145, the sequence number is 21), the last 8 characters are a random 32-bit integer.

It’s interesting that this feature isn’t implemented in the same way in previous versions of Windows (8.1 and below). In Windows 7, a deleted file which is still open remains in its original directory (just like any normal file), but it can’t be opened anymore.

5 thoughts on “The “\$Extend\$Deleted” directory”

  1. To clean up $Extend just run chkdsk – I had to abort it after a couple of hours at 100% complete. The other problem was my boot order was messed up so – this meant the system booted into the efi shell. I had to set the boot manager to first in the boot order.

    Bren

    Like

  2. The change in behavior between versions is kind of a change of the Win32 API-level defaults. Both modes of deletion are still available on the Native API level.

    The classical approach is NtSetInformationFile(FileDispositionInformation) or opening with FILE_DELETE_ON_CLOSE. They don’t move the file to \$Extend\$Deleted; the file remains visible (but inaccessible for new open requests) until all other handles are closed. Attempts to reopen such file result in STATUS_DELETE_PENDING error (which translates to Win32’s ERROR_ACCESS_DENIED).

    Since RS1, there is also support for posix-style deletion which is exposed via NtSetInformationFile(FileDispositionInformationEx) with the FILE_DISPOSITION_POSIX_SEMANTICS flag. When closing the file, it indeed moves to \$Extend\$Deleted as described. Similar to the previous case, it can then only be accessed via other existing handles. Once these handles are closed, the file gets fully deleted.

    So at some point, DeleteFile API switched to the new method and started using posix-style deletion.

    Like

Leave a comment