How to delete Windows NTFS hard link (mklink /h) while original is in use?

On a Windows NTFS file system, I have a file (say, orig.mp3). I open this file, through this path orig.mp3, in such a way that it is in use (say, by playing it in VLC).

Then I create a hard link (cmd /c mklink /h link.mp3 orig.mp3). This results in two NTFS paths pointing to exactly the same file.

Finally I try to delete linked file again (del link.mp3, or delete in Windows Explorer).

This fails with an error: "The process cannot access the file because it is being used by another process."

Why? And more importantly: how can I avoid this (apart from making sure no process has the original file in use)? Can I perhaps tell Windows to do a 'delayed delete', so that the linked file is automatically deleted when the original is no longer in use?


Solution 1:

This is quite expected behavior, the hard link is just another name for the same file. E.g., if you have file A.PDF, create hard link B.PDF to the same file, it doesn't matter whether the file is opened under the name A.PDF or B.PDF - it's still the same file, so if this file is simply opened, you can't delete either link.

The actual reason is that the name is stored as an attribute in the file record of master file table (in case of NTFS) and since the file is opened, you can't delete either link (you can't modify opened file).

In this case there's nothing like original file, since both names belong to the same (and the only one) file and both names are equal. The file is actually deleted when link count reaches zero.

Solution 2:

While the above answers are not exactly wrong about WHEN and WHY you can or cannot delete a hardlink the more precise reason can be found looking at this link:

FILE_SHARE_DELETE link in the CreateFileA documentation.

If this flag is not specified, but the file or device has been opened for delete access, the function fails.

A CreateFile call will fail when requesting DELETE dwDesiredAccess if any handles are open to that file (including its hardlink-peers) where those handles were NOT opened with FILE_SHARE_DELETE permission.

That is the general reason why attempting to delete a hardlink may fail (error code 5 access violation); requiring that all open hardlink-peers of that file must be closed in order to delete the hardlink.

It is a common pattern to create/delete hardlinks to executables (perhaps nodejs/npm) for nix-busybox style EXE name-aliasing to share a single EXE binary.

For executables that are hardlinked, deleting a hardlink to an executable which has a hardlink-peer running WILL FAIL because the Windows loader DOES NOT open executable files with FILE_SHARE_DELETE permission. Which makes managing hardlinks via deletion for install remove/updating quite annoying when service-exes are running where you used shared-exe hardlink-peer naming to create those service-exes . As a pattern, use symlinks instead if that is your issue. Although, unlike with hardlinks, your service-exes in any process-viewer (or similar process enumeration api) will use the symlinks target-name not the symlink name [sigh]).

It is correct that hardlinks share an NTFS file description.

i.e., same structure on volume. Also note that hardlinks share the same NTFS FILE_ID_INFO see GetFileInformationByHandleEx - FileIdInfo (0x12).

But that is NOT the reason you can't delete a hardlink while a peer-link is open. This is evidenced further by the fact that EVEN when a peer-link is open you CAN create more hardlinks to ANY of the peer hardlinks; even though you can't delete them while any peer is open without FILE_SHARE_DELETE permission.

How you might go about deleting a hardlink with a peer-link open

  1. Use a tool that will show you the hardlink-peers.
  2. Use a tool that will show you processes with a handle that is open to the hardlink or its peers. Manually close/kill them.
  3. Use a tool that is a file-watcher or use file-watcher apis to delete the hardlink when all the hardlink-peers are closed (via #2).
  4. Use the above mentioned Win32 MoveFileEx function with the MOVEFILE_DELAY_UNTIL_REBOOT and wait/require a reboot to delete the hardlink.

For reference, I have a toolset that uses these type of hardlink patterns. It can perform all the above (it is a general shell and dynamic language ess). The toolset is EdgeShell with a single no-install binary named afm.exe.

API Note: to do the same actions as DeleteFile the dwFlagsAndAttributes need only be FILE_FLAG_DELETE_ON_CLOSE and FILE_FLAG_OPEN_REPARSE_POINT (to properly handle symlink and junction reparse points rather than their targets).

API Note: you can enumerate all the hardlink peers using FindFirstFileNameW, FindNextFileNameW, and FindClose. A hardlink-peer is just another NTFS directory node entry name reference (pathname) to an NTFS file node. Since they share the NTFS volume's file-itself and hardlink-xref (backpointers) to dir-nodes information is kept in that file-node, hardlinks have to be on the same NTFS volume.

Note: Copying to a hardlinked-file and NTFS file-sharing of reparse-points

It is probably VERY unintuitive that if you use most command line or editor tools to COPY a file onto a hardlink, it usually will DELETE/BREAK the hardlink and create a NEW standalone copy of that file.

If your goal was to actually replace the contents of the hardlink file so that the hardlink and ALL its peers would share the copied data then you typically need a tool (w/wo flag options) that is hardlink aware for that action.

It is best, if you don't have such tools, to "open" the hardlink file in question and "write" the desired new content in place (do not use a generic file-copy tool). Be aware that many IDE editors (VsCode, Visual Studio) will use std c-lib apis that actually delete then replace the file (breaking your hardlink). Which can get very confusing.

The Windows world has always had great NTFS capabilities but has long lacked nix knowledge and tools for handling hardlinks, symlinks (and junctions).

FILE SHARES and LINKS: In point of fact, JUNCTIONS are awesome. Especially when you understand the real difference between how JUNCTIONS and SYMLINK reparse points are treated in a network file-share of an NTFS volume.

I.e., a SYMLINK is resolved on the CLIENT machine, but a JUNCTION is resolved on the SERVER machine.

Which means that an NTFS volume with a symlink to say C:\something will look on the C drive of the client machine (because the link is resolved on the client), but a JUNCTION will look on the C drive of the server (because the link is resolved on the server) and thus resolves path on server and doesn't expose the junction-reparse to the client.

For configuring NTFS share behavior see

fsutil behavior set SymlinkEvaluation

symlinkevaluation <symboliclinktype> Controls the kind of symbolic links that can be created on a computer. Valid choices are:

  1. Local to local symbolic links, L2L:{0|1}
  2. Local to remote symbolic links, L2R:{1|0}
  3. Remote to local symbolic links, R2L:{1|0}
  4. Remote to remote symbolic links, R2R:{1|0}

Solution 3:

As detailed in Robert Goldwein's answer, such a hard link cannot be deleted while the file is in use. However, a delayed delete turns out to be possible.

Damon's comment on this question suggests to use movefile from the Sysinternals Suite.

In my case, where I want to do this from PowerShell, I can use Lee Holmes's Move-LockedFilelink.mp3 $null, to have Windows delete the file at the next boot.

Both of the above use the Win32 MoveFileEx function with the MOVEFILE_DELAY_UNTIL_REBOOT flag.

Update: See https://gist.github.com/marnix/7565364 for a Remove-File-Eventually which I just hacked up. No guarantees. :-)

Solution 4:

Use the FSUTIL tool to maintain the symlinks safely.

http://technet.microsoft.com/en-ca/library/cc753059.aspx

fsutil reparsepoint delete link.mp3

would remove the hardlink while preserving orig.mp3