38

Solved:

The Problem:

Our software is in large part an interpreter engine for a proprietary scripting language. That scripting language has the ability to create a file, process it, and then delete the file. These are all separate operations, and no file handles are kept open in between these operations.

(I.e. during the file creation, a handle is created, used for writing, then closed. During the file processing portion, a separate file handle opens the file, reads from it, and is closed at EOF. And finally, delete uses ::DeleteFile which only has use of a filename, not a file handle at all).

Recently we've come to realize that a particular macro (script) fails sometimes to be able to create the file at some random subsequent time (i.e. it succeeds during the first hundred iterations of "create, process, delete", but when it comes back to creating it a hundred and first time, Windows replies "Access Denied").

Looking deeper into the issue, I have written a very simple program that loops over something like this:

while (true) {
    HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ,
                               NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return OpenFailed;

    const DWORD dwWrite = strlen(pszFilename);
    DWORD dwWritten;

    if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
        return WriteFailed;

    if (!CloseHandle(hFile))
        return CloseFailed;

    if (!DeleteFileA(pszFilename))
        return DeleteFailed;
}

As you can see, this is direct to the Win32 API and is pretty darn simple. I create a file, write to it, close the handle, delete it, rinse, repeat...

But somewhere along the line, I'll get an Access Denied (5) error during the CreateFile() call. Looking at sysinternal's ProcessMonitor, I can see that the underlying issue is that there is a pending delete on the file while I'm trying to create it again.

Questions:

  • Is there a way to wait for the delete to complete?
  • Is there a way to detect that a file is pending deletion?

We have tried the first option, by simply WaitForSingleObject() on the HFILE. But the HFILE is always closed before the WaitForSingleObject executes, and so WaitForSingleObject always returns WAIT_FAILED. Clearly, trying to wait for the closed handle doesn't work.

I could wait on a change notification for the folder that the file exists in. However, that seems like an extremely overhead-intensive kludge to what is a problem only occasionally (to wit: in my tests on my Windows 7 x64 E6600 PC it typically fails on iteration 12000+ -- on other machines, it can happen as soon as iteration 7 or 15 or 56 or never).

I have been unable to discern any CreateFile() arguments that would explicitly allow for this ether. No matter what arguments CreateFile has, it really is not okay with opening a file for any access when the file is pending deletion.

And since I can see this behavior on both an Windows XP box and on an x64 Windows 7 box, I am quite certain that this is core NTFS behavior "as intended" by Microsoft. So I need a solution that allows the OS to complete the delete before I attempt to proceed, preferably without tying up CPU cycles needlessly, and without the extreme overhead of watching the folder that this file is in (if possible).

1 Yes, this loop returns on a failure to write or a failure to close which leaks, but since this is a simple console test application, the application itself exits, and Windows guarantees that all handles are closed by the OS when a process completes. So no leaks exist here.

bool DeleteFileNowA(const char * pszFilename)
{
    // Determine the path in which to store the temp filename
    char szPath[MAX_PATH];
    strcpy(szPath, pszFilename);
    PathRemoveFileSpecA(szPath);

    // Generate a guaranteed to be unique temporary filename to house the pending delete
    char szTempName[MAX_PATH];
    if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
        return false;

    // Move the real file to the dummy filename
    if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
        return false;

    // Queue the deletion (the OS will delete it when all handles (ours or other processes) close)
    if (!DeleteFileA(szTempName))
        return false;

    return true;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mordachai
  • 9,412
  • 6
  • 60
  • 112
  • 3
    You sure all handles are closed? Because what you wrote is exactly described on MSDN: "If you call CreateFile on a file that is pending deletion as a result of a previous call to DeleteFile, the function fails. The operating system delays file deletion until all handles to the file are closed. GetLastError returns ERROR_ACCESS_DENIED." – Kra Sep 21 '10 at 20:23
  • 2
    I have created a console app that does the above code in a loop. It fails eventually... usually around iteration 12000-15000. So unless you see some way that the above 8 lines of code leaks a handle, I think it's quite impossible (at least for this test applet). – Mordachai Sep 21 '10 at 20:27
  • To be more humble about it: that was our initial assumption as well. But after chasing ghosts for days, I simplified it to the above, and as I said, looping that exhibits the problem consistently (if randomly). – Mordachai Sep 21 '10 at 20:29
  • 1
    @sbi - thanks - but the last handle _is_ closed. That's the problem: Windows is treating it as if it weren't closed when it is. I need some way to synchronize with the OS to ensure that my code waits for the actual deletion to complete. – Mordachai Sep 21 '10 at 20:30
  • In your sample program, if WriteFile fails or it was only a partial write for some reason, then you'll not close the file handle. So maybe check there are no short-circuit returns that could leak the file handle. Using a guard class that closes the handle in the dtor is the safest approach. – Sean Fausett Sep 21 '10 at 20:30
  • @Sean: Yes, in a non-dummy-example-code-clip, I would guard against all possible exits using RAII. However, for this super-simple example, which simply aborts the loop when any failure condition arises, I am guaranteed that the handle is closed. And the loop is never trying to continue with a leaked HFILE. – Mordachai Sep 21 '10 at 20:32
  • 3
    @Mordachai: I already saw this and had my comment deleted. – sbi Sep 21 '10 at 20:36
  • I wonder if there is some sort of sync() call for windows. A quick search failed to turn it up, but it may be worth looking for... –  Sep 21 '10 at 20:45
  • Interesting and well documented question, +1. BTW, the equivalent of sync on Windows should be the FlushFileBuffers API. – Matteo Italia Sep 21 '10 at 20:54
  • 1
    Hans's explanation sounds like it hits the nail on the head. It just doesn't sound plausible to me that the file system itself could be confused about its internal state, i.e. that it would believe a deleted file still existed only because it had not yet completed all of the asynchronous housekeeping associated with deleting that file. – Martin B Sep 21 '10 at 20:54
  • What really begs the question here is why doesn't the OS do what I'm doing above (allow another file to be created by the same name while the old one still is pending deletion). It doesn't seem like there is any good reason not to... – Mordachai Sep 22 '10 at 16:07
  • 4
    I had this issue caused by the Windows Search service, it would noticed I made a directory and lock it to index it, at the same time I attempted to delete it and got error 5. Its easy to reproduce this by creating and deleting dirs/files in a loop with the indexer enabled. – paulm Nov 18 '13 at 16:26
  • 4
    It was `MsMpEng` [in my case](http://superuser.com/a/709847/386891), which blocked the file. I have discovered MS confirmation that index/[antivirus SW can block the file](http://goo.gl/rXWHEL). Now, I therefore accustomed to delete in retry loop or better create new temp file instead of reusing single name, with a hope that single delete will be effective ultimately. Now, I have got a similar issue. `Gcc p2` fails in `gcc p.c > p.exe && p && gcc p2.c > p.exe && p ...` because just-terminated p.exe is blocked and nothing in filemon but p process accesses the file. – Val Apr 08 '15 at 12:30

13 Answers13

22

There are other processes in Windows that want a piece of that file. The search indexer is an obvious candidate. Or a virus scanner. They'll open the file for full sharing, including FILE_SHARE_DELETE, so that other processes aren't heavily affected by them opening the file.

That usually works out well, unless you create/write/delete at a high rate. The delete will succeed but the file cannot disappear from the file system until the last handle to it got closed. The handle held by, say, the search indexer. Any program that tries to open that pending-delete file will be slapped by error 5.

This is otherwise a generic problem on a multitasking operating system, you cannot know what other process might want to mess with your files. Your usage pattern seems unusual, review that first. A workaround would be to catch the error, sleep and try again. Or moving the file into the recycle bin with SHFileOperation().

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Yes, it might be sensible to avoid this pattern entirely! lol - unfortunately, I'm in a role akin to a language developer who has programmers who use said language. I can tell them that some patterns are better avoided, but in the end, they write their software (scripts) and I have to do my best to deal with their limited capacity to pay attention to "best practices". – Mordachai Sep 21 '10 at 20:52
  • Well, trying to hide *real* operating system behavior from those script programmers would be a drastic mistake. Improve your api, provide a way for those lowly souls to handle the problem by themselves. Error code, exception, whatever. Avoid the God attitude, you'll just create something that everybody think will suck. Because they've got that problem with their script that doesn't work without a diagnostic and God is not answering their email. – Hans Passant Sep 21 '10 at 21:05
  • 1
    At the level of C/C++/Java/any other principle programming language, I think you're right. And my gut totally agrees with you. However, at the level of "simplified scripting language" - we generally live by the mantra "if it's a reasonable thing for the script writer to do, then the engine should make it happen." And a loop that involves a sub-component that generates a given file, another the processes & then deletes the file, and then looping those two blocks for some larger gain is arguably _reasonable_. So in this case we're likely to hide this complexity from the script writer. – Mordachai Sep 22 '10 at 13:53
  • 8
    To be fair, this is a problem specific to windows and not a generic os problem. Other operating systems easily handle this by allowing their equivalent of DeleteFile to remove the name without removing the file, so indexers do not keep files from being deleted. UNIX systems are an example of this behaviour. The problem exists because Win32 (not NTFS) requires a name for every open file. – Remember Monica May 01 '13 at 03:38
  • 1
    Will such services (like search indexer) also try to read temporary files? – foresightyj Aug 28 '15 at 02:25
  • An additional explanation starts at about 7:30 in this talk from CppCon 2015: https://www.youtube.com/watch?v=uhRWMGBjlO8 – Adrian McCarthy Jun 01 '18 at 15:42
19

First rename the file to be deleted, and then delete it.

Use GetTempFileName() to obtain a unique name, and then use MoveFile() to rename the file. Then delete the renamed file. If the actual deletion is indeed asynchronous and might conflict with the creation of the same file (as your tests seems to indicate), this should solve the problem.

Of course, if your analysis is right and file operations happen somewhat asynchronous, this might introduce the problem that you attempt to delete the file before the renaming is done. But then you could always keep trying to delete in a background thread.

If Hans is right (and I'm inclined to believe his analysis), then moving might not really help, because you might not be able to actually rename a file that's open by another process. (But then you might, I don't know this.) If that's indeed the case, the only other way I can come up with is "keep trying". You would have to wait for a few milliseconds and retry. Keep a timeout to give up when this doesn't help.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
sbi
  • 219,715
  • 46
  • 258
  • 445
  • Cool idea. I'm not sure if it will work for our real-scenario (which is much more complex than my question), but it might! :) – Mordachai Sep 21 '10 at 20:48
  • @Mordachai: You might want to read my second edit. If Hans is right, this might not help. But you can always try it in your test program first, to see whether complicating your real app is actually worth trying. – sbi Sep 21 '10 at 20:52
  • Yeah, I think Hans is right too. :( So this just pushes the problem back one step, but solves nothing. Sigh. – Mordachai Sep 21 '10 at 20:58
  • 3
    +1. probably doesn't matter if Hans is right. You can rename open files. This is no problem because file handles are mostly independent of file names. The name only matters at the time you create a file handle from a file name. The hypothetical process with an open file handle won't be affected. – MSalters Sep 22 '10 at 10:57
  • Interesting idea. I could take all files that are to be deleted, rename them to a temp filename, then delete that, which will happen *whenever* w/o affecting the script writers. Hmmm... ;) – Mordachai Sep 22 '10 at 13:50
  • @MSalters: I didn't expect renaming to work for open files, but didn't rule it out either. ("But then you might, I don't know this.") If you are right and it works, this might indeed solve the problem. – sbi Sep 22 '10 at 13:54
  • I'm giving this a shot now. I'll let you know. BTW: how in the world did the "answer" switch to here? I was debating moving it, but then it seemed to move itself! huh? – Mordachai Sep 22 '10 at 14:02
  • @Mordachai: You must have switched it by clicking on the check mark next to my answer. Not that I'd mind it, but I think Hans' answer is the better one (his first up-vote was from me), as it correctly explains what happens, while I just made a shot in the dark. – sbi Sep 22 '10 at 14:11
  • Looking into this idea: but running into problems. Mainly that the GetTempFileName cannot give me a guaranteed to be unique filename unless it *creates* the file for me, which makes moving the old file to this name problematical (i.e. the new file has to be deleted before the old one can be moved!). So although I can think of a hack (prefix [deleting] to the target filename arbitrarily) it's perhaps not that easy to do in practice (i.e. I may end up having to do a form of uniqueness generation myself) – Mordachai Sep 22 '10 at 14:38
  • 1
    This does seem to work! Basically, I am creating a temporary file (actually creating it via GetTempFileName), and then using MoveFileEx to overwrite the unique temp file with the real one, then issuing the DeleteFile on the temp filename. So far, I can't get that to go wrong. I expect there's a performance hit - it cannot be as fast to do all of this extra overhead, but to insulate our scipters from the subtleties of the multitasking filesystem, it's probably worth it. – Mordachai Sep 22 '10 at 15:01
  • 1
    @Mordachai: `GetTempFileName()` _has_ to create the file, otherwise a equally named file could be created by someone else between the time you get the unique file name and when you actually create the file. Anyway, glad you got it working. – sbi Sep 22 '10 at 16:17
  • Yeah, I get why it works the way it does. I was just worried that this would mean that the move operation would fail while someone had a handle open to the original file. Apparently, this is not the case (though I cannot be absolutely certain: 1.5 million iterations says it's working, but...) – Mordachai Sep 22 '10 at 16:23
5

Silly suggestion - since it fails so infrequently, simply wait some milliseconds on failure and try again.

Or, if latency is important, switch to another file name, leaving the old file to be deleted later.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
4

Is there a way to detect that a file is pending deletion?

Use the GetFileInformationByHandleEx function with the FILE_STANDARD_INFO structure.

But the function can't solve your problem. sbi's solution neither.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Benjamin
  • 10,085
  • 19
  • 80
  • 130
  • 1
    Erm, I don't think that this is possible [in our situation]. Quite frankly, it's not possible to get a handle for a file which is pending deletion. So by the time the CreateFile() executes, the file is pending deletion, but it is impossible to obtain a handle on it to check. :( – Mordachai Sep 23 '10 at 14:20
  • Onces a file pend as deletion, you can't open the file again. It is only possible when you got another handle before the file deleted. – Benjamin Sep 23 '10 at 14:26
  • Right - so any useful solution would need to answer the question "why is access denied" when I call CreateFile()? If there is a way to ask the OS "is the file pending delete" at that point in time, then this would be useful. Or if there was a way to get CreateFile() to be more forthcoming with details instead of a blank "denied" then that would be more useful. – Mordachai Sep 23 '10 at 16:09
  • Ultimately, unless someone can disabuse me as to why MS fails to either report more detailed info or to hide these shenanigans under the covers of the OS, I have to conclude that this is a very stupid, poorly thought out mechanism on the part of Microsoft. I'm not against being disabused - If there is a genuinely good reason, I'm all ears. But so far, it just seems like a confluence of inadequately thought out design decisions on the NTFS & Win32API team's parts. – Mordachai Sep 23 '10 at 16:11
  • 8
    In kernel-mode, NtCreateFile returns STATUS_DELETE_PENDING. It might be you wanted. But I/O manager translates the error to ERROR_ACCESS_DENIED before return CreateFile. http://support.microsoft.com/kb/113996 I don't know why they not to transrate it to ERROR_DELETE_PENDING. You can see ERROR_DELETE_PENDING when you open a file and delete and delete again before close the handle you opened. – Benjamin Dec 07 '10 at 10:10
  • As the link posted by Benjamin is down, here's the archived version of the linked site: https://web.archive.org/web/20150317121919/http://support.microsoft.com/en-us/kb/113996 – riQQ Nov 20 '18 at 15:44
4

This is maybe not your particular issue, but it's possible so I suggest you get out Process Monitor (Sysinternals) and see.

I had exactly the same problem and discovered that Comodo Internet Security (cmdagent.exe) was contributing to the problem. Previously I had a dual-core machine, but when I upgraded to an Intel i7 suddenly my working software (jam.exe by Perfore software) no longer worked because it had the same pattern (a delete then create, but no check). After debugging the problem I found GetLastError() was returning access denied, but Process Monitor reveals a 'delete pending'. Here is the trace:

10:39:10.1738151 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Read Attributes, Delete, Disposition: Open, Options: Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
10:39:10.1738581 AM jam.exe 5032    QueryAttributeTagFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Attributes: ANCI, ReparseTag: 0x0
10:39:10.1738830 AM jam.exe 5032    SetDispositionInformationFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Delete: True
10:39:10.1739216 AM jam.exe 5032    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1739438 AM jam.exe 5032    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1744837 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1788811 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1838276 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1888407 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1936323 AM System  4   FASTIO_ACQUIRE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS SyncType: SyncTypeOther
10:39:10.1936531 AM System  4   FASTIO_RELEASE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1936647 AM System  4   IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1939064 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1945733 AM cmdagent.exe    1188    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1946532 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1947020 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1948945 AM cfp.exe 1832    QueryOpen   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   FAST IO DISALLOWED  
10:39:10.1949781 AM cfp.exe 1832    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   NAME NOT FOUND  Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
10:39:10.1989720 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0, OpenResult: Created

As you can see, there is a request to delete followed by several attempts to open the file again by jam.exe (it's an fopen in a loop). You can see cmdagent.exe presumably had the file open as it closes its handle and then suddenly jam.exe is able to now open the file.

Of course, the suggested solution to wait and try again, and it works just fine.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Craig
  • 41
  • 1
  • 1
    Yes, this is the same pattern. In my case it was NOD32 Antivirus, but in general, the issue will (potentially) exist on all Windows machines (and possibly other OSes, dep. on how they handle their file system). It is not necessarily a design flaw in the OS (though why they didn't do what I did [release the dir entry immediately upon delete, even if handles still open on it] I don't know). UPSHOT: Either software needs to always be written to "wait & try again" or as marked "answered" rename then delete. – Mordachai Dec 07 '10 at 14:24
  • 2
    BTW - this would be a great topic for Raymond Chen. Too bad I can't get this into his queue :( – Mordachai Dec 07 '10 at 14:43
  • https://devblogs.microsoft.com/oldnewthing/20120907-00/?p=6663 – jacobsee Jan 15 '22 at 00:06
3

Since you're creating a new file, processing it, then deleting it, it sounds like you don't really care about what the file name is. If that's truly the case, you should consider always creating a temporary file. That way, each time through the process, you don't have to care that the file didn't yet get deleted.

Jacob
  • 77,566
  • 24
  • 149
  • 228
  • 1
    -1 his question specifies that he doesn't get to control the content of the scripts causing this – Elemental Sep 21 '10 at 21:27
  • 1
    @Elemental, exactly. He said the *content* of the scripts, not the *location* of the scripts, so the downvote is invalid. – Jacob Sep 21 '10 at 21:32
  • @Jocab - unfortunately the scripts themselves contain the commands including the delete command which uses the file name directly (literally) and he can't change the scripts to use temp file names instead. – Elemental Sep 22 '10 at 06:53
  • 1
    It is a nice idea, Jacob, but indeed our script writers are in control of the name of the file. And although this particular scenario has arisen, in general, it does not. It is impossible at the language-level (scripting interpreter) to assume the nature of the file, and enforce any particular rules around it (it's just an output filename in one context, an input filename in another, and a target of a delete command in yet another, all unrelated as far as the interpreter knows). – Mordachai Sep 22 '10 at 13:44
  • 2
    The script interpreter could create uniquely named files on the filesystem and maintain a mapping of the names used in the script to the actual filesystem names. Upon completion of the script, the interpretter could rename the remaining files to the names desired by the script, in order to create the appropriate side effects. – Adrian McCarthy Feb 13 '15 at 16:55
2

I actually had the same issue while using the LoadLibrary(path). I couldn't delete the file in path.

The solution was to "close the handle" or use the FreeLibrary(path) method.

NOTE: Please read the "Remarks" on MSDN regarding the FreeLibrary().

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
OhadM
  • 4,687
  • 1
  • 47
  • 57
1

According to [1], you could use NtDeleteFile to avoid the asynchronous nature of DeleteFile. Also [1] gives some details on how DeleteFile works.

Unfortunately the official documentation on NtDeleteFile [2] doesn't mention any particular details on this issue.

[1] Undocumented functions of NTDLL

[2] ZwDeleteFile function

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Christian K.
  • 605
  • 8
  • 10
  • 2
    This is wrong. `NtDeleteFile` only makes setting the delete disposition more efficient by skipping the two-step `NtOpenFile`, `NtSetInformationFile` sequence and not requiring the I/O manager to allocate a real File object. Of course, it still has to allocate IRP for the set-information request and call the filesystem driver, and of course the file still won't be unlinked until the the last File reference is closed/cleaned up. – Eryk Sun Apr 11 '18 at 10:18
1

If CreateFile returns INVALID_HANDLE_VALUE then you should determine what GetLastError returns in your particular situation (pending delete) and loop back to CreateFile based on that error code only.

The FILE_FLAG_DELETE_ON_CLOSE flag might buy you something.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pineapple
  • 41
  • 4
  • 1
    I think _Windows replies "Access Denied"_ is what `GetLastError()` returns. – sbi Sep 21 '10 at 20:41
  • 1
    The GetLastError() is always "(5) Access Denied" -- which is a pretty lame, limited response from the OS. If it was "(91929) Pending Delete" then that would be GREAT! :) – Mordachai Sep 21 '10 at 20:46
  • Thanks for the ideas, Pineapple. Unfortunately, the scripting engine can't know when the script is going to delete the target file (if ever), so it can't mark the file for deletion prior to the script asking for that to happen. And once that does happen, we're at the mercy of the OS to complete the operation. If the script tries to do another command which generates that file again and the previous hasn't completed, then they'll get this error. It's not really under my control what the script writer does (and on the face of it, that script is reasonable) – Mordachai Sep 22 '10 at 13:48
1

The best answer was given by sbi, but in the interest of completeness, some people might also want to know about a new way now available from Windows 10 RS1/1603.

It involves calling the SetFileInformationByHandle API with class FileDispositionInfoEx, and setting flags FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS. See the full answer by RbMm.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Djof
  • 603
  • 1
  • 7
  • 20
1

I just had this exact issue and took TWO steps to address it; I stopped using C/C++ stdlib apis and ::DeleteFile(..), and switched to:

  1. ::MoveFileEx(src,dest,MOVEFILE_WRITE_THROUGH);. See: MOVEFILE_WRITE_THROUGH

  2. h = ::CreateFile(DELETE | SYNCHRONIZE,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_OPEN_REPARSE_POINT); ::CloseHandle(h);

The above are pseudo calls showing the relevant flags, specifically note that there is NO sharing on the CreateFile call used to achieve delete.

Together they gave me better precision on the rename and delete semantics. They are working in my code now and have improved the precision and control from other threads/processes (watching the file-system for changes) interjecting actions on the file due to latencies [or sharing] in the other rename and/or delete APIs. Without that control, a file set to delete when its last kernel-handle was closed might actually languish open until the system was rebooted, and you might not know.

Hopefully those feedback snippets might prove useful to others.

Addendum: I happen to use hardlinks for a portion of the work I do. It turns out that although you can create hardlinks on a file that is OPEN, you cannot delete ANY of them until all handles to ANY of the underlying data-stream(s) to that NTFS file are closed. That is weird since:

Which would lead you to think that only the last hardlink should be non-deletable while the kernel has one or more open-file handles referring to the hardlinked NTFS File's MFT-Entry/ATTRs. anyway, just another thing to know.

smallscript
  • 643
  • 7
  • 13
  • Are you calling first MoveFileEx and then CreateFile? For me it doesn't delete the file on close, not sure why. – Mecanik Oct 30 '21 at 08:31
1

On Windows Vista/Windows 7 there is DeleteFileTransacted which deletes a file using transactions which ensures they are deleted (flushes file buffers, etc.). For Windows XP compatibility this is not an option though.

Another idea how this might be done is use OpenFile() with the flag OF_CREATE which sets the length to zero if the file exists or creates it if it doesn't and then to call FlushFileBuffers on the file handle to wait for this operation (making the file zero length) to complete. On completion, the file is of size 0 and then simply call DeleteFile.

You can later test if the file exists or if it has zero-length to treat it the same way.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Coder12345
  • 3,431
  • 3
  • 33
  • 73
  • 3
    DeleteFileTransacted actually shares the behavior of DeleteFile that the file will be deleted when the last handle is closed. Quote: "The DeleteFileTransacted function marks a file for deletion on close." from https://msdn.microsoft.com/en-us/library/windows/desktop/aa363916(v=vs.85).aspx – Christian K. Nov 26 '15 at 14:25
-2

I think this is just by poor design in the file system. I have seen the same problem when I worked with communication ports, opening/closing them.

Unfortunately I think the simplest solution would be to just retry to create the file a number of times if you get an INVALID_HANDLE_VALUE. GetLastError() might also give you a better way of detecting this particular INVALID_HANDLE_VALUE.

I would have preferred overlapped I/O, but their CloseHandle() and DeleteFile() don't handle overlapped operations :(

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Max Kielland
  • 5,627
  • 9
  • 60
  • 95