The curse of the current directory


The current directory is both a convenience and a curse. It's a convenience because it saves you a lot of typing and enables the use of relative paths. It's a curse because of everything else.

The root cause of this curse is that the Windows NT family of operating systems keeps open a handle to the process's current directory. (Pre-emptive Yuhong Bao comment: The Windows 95 series of operating systems, on the other hand, did not keep the current directory open, which had its own set of problems not relevant to this discussion.)

The primary consequence of this curse is that you can't delete a directory if it is the current directory of a running process. I see people stumble upon this all the time without realizing it.

I am trying to delete a directory X, but when I try, I get the error message The process cannot access the file because it is being used by another process.. After some hunting around, I found that directory X is being held open by someapp.exe. Why the heck is someapp.exe holding my directory open, and how do I get it to stop?

The value of someapp.exe changes over time, but the underlying problem is the same. And when this happens, people tend to blame someapp.exe for stupidly holding a directory open.

Most of the time, someapp.exe is just a victim of the curse of the current directory.

First, let's take the case where someapp.exe is explorer.exe. Why is the current directory of Explore set to this directory?

Well, one reason might be another curse of the current directory, namely, that the current directory is a process-wide setting. If a shell extension decided to call SetCurrentDirectory, then that changes the current directory for all of Explorer. And if that shell extension doesn't bother to call SetCurrentDirectory a second time to reset the current directory to what it was, then the current directory gets stuck at the new directory, and Explorer has now been conned into changing its current directory permanently to your directory.

Mind you, the shell extension might have tried to do the right thing by setting the current directory back to its original location, but the attempt might have failed:

GetCurrentDirectory(Old) // returns C:\Previous
SetCurrentDirectory(New) // changes to C:\Victim
.. do stuff ..
SetCurrentDirectory(Old) // changes to C:\Previous - fails?

That second call to SetCurrentDirectory can fail if, while the shell extension is busy doing stuff, the directory C:\Previous is deleted. Now the shell extension can't change the directory back, so it's left stuck at C:\Victim, and now you can't delete C:\Victim because it is Explorer's new current directory.

(The preferred behavior, by the way, is for the shell extension not to call SetCurrentDirectory in the first place. Just operate on full paths. Since the current directory is a process-wide setting, you can't be sure that some other thread hasn't called SetCurrentDirectory out from under you.)

Mind you, making the current directory a per-thread concept doesn't solve this problem completely, because the current directory for the thread (if such a thing existed) would still have a handle open until the thread exited. But if the current directory had been a per-thread concept, and if the thread were associated with an Explorer window, then closing that window would at least encourage that thread to exit and let you unstick the directory. That is, unless you did a Terminate­Thread, in which case the handle would be leaked and your attempt to release the handle only ensures that it never happens. (Note to technology hypochondriacs: This paragraph was a hypothetical and consequently will be completely ineffective at solving your problem.)

The story isn't over yet, but I'll need to digress for a bit in order to lay the groundwork for the next stage of the curse.

Bonus chatter: Hello, people. "The story isn't over yet." Please don't try to guess the next chapter in the story.

Comments (44)
  1. Adam Rosenfield says:

    A shell extension changing Explorer's current directory also violates the axiom of "don't go messing around with resources that aren't yours", which you've talked about before.  The current directory belongs to Explorer.  Unless it gives you explicit permission to change it, you should not do so.

  2. Dan Bugglin says:

    Interestingly enough I just upgraded my Cygwin today; they were dealing with current directory quirkiness.

    Apparently Linux allows you to delete another process' CWD, or even your own, but of course such attempts fail in Windows and of course Cygwin is striving for more accurate Linux API emulation.

    So their fix for this in 1.7.6 was to set the actual CWD to somewhere else, and Cygwin's APIs would simulate your desired CWD, so then you could delete it and so forth since Windows didn't consider it your CWD.  Of course this broke apps which used Cygwin APIs to change directory and then Windows APIs to work with current directory files (whoops!) so they reverted it in 1.7.7.  Guess the solution was worse than the problem in this case.

  3. Medinoc says:

    Another thing that messes with the current directory is GetOpenFileName(). That's something to keep in mind.

  4. NB says:

    Interesting.

    Now I know not to blame Explorer when it's preventing me from deleting a directory. I'll just blame Windows itself instead. :)

  5. Jack B Nimble says:

    At least now I know, so in the future when windows won't let me delete a folder I'll just write an app to SetCurrentDirectory to something I won't delete (such as C:Windows).

  6. Joshua says:

    I feel like writing a system-wide hook that causes all handles to be opened with FILE_SHARE_DELETE.

    Now if you try to delete something it will go away when the last handle is closed.

    Problem solved, mostly. [Acrobat 10 bombs with such a hook loaded]

  7. f0dder says:

    Unfortunately, just refraining from (direct use of) SetCurrentDirectory isn't enough – I went through the pains of designing a non-recursive filesystem traverser that dealt with full paths, only to find out that FindFirstFile/FindNextFile internally set the current directory.

    Also, a reference to a process's startup directory (no, not the directory of the .exe, but the cwd-at-startup) is held throughout a process' lifetime, which has implications for explorer right-click integration that does the simple registry-key stuff intead of COM fancyness.

    [I don't know who's changing the current directory, but it's not FindFirstFile/FindNextFile. I just checked (Windows 7) with write breakpoints on the current directory field and it never got written to. -Raymond]
  8. Alex Grigoriev says:

    @f0dder:

    It's probably not that FFF sets the current directory. It's that the find handle references the target directory, preventing you from deleting it. If you forget to use FindClose, or wrongly use CloseHandle, then the target directory will still be open.

  9. Alex Grigoriev says:

    @Jack B Nimble:

    That would be pointless. A current directory is per-process concept. It's not returned back to the "parent" process.

  10. Dheeraj says:

    The real WTF is that explorer does not tell you which process is using the directory.

    Fortunately I've installed Unlocker that helps me out:

    ccollomb.free.fr/unlocker

  11. Sunil Joshi says:

    The link that Dheeraj has posted comes up as Malware according to ie9 beta.

  12. KTC says:

    "Report that this site does not contain threats"

  13. Wizou says:

    Another thing that messes with the current directory is GetOpenFileName(). That's something to keep in mind.

    Right! Even when you specified OFN_NOCHANGEDIR, the function DO change the current directory while it is running. OFN_NOCHANGEDIR restores the current directory when the function exits.

    So this can be a real pain in a multi-thread application!

  14. James Curran says:

    However, it should be noted that in the C:Previous | C:Victim example, had the shell extension NOT changed the current directory, then you would have been unable to delete C:Previous.  In other words, in this case, it did not cause the problem, it merely relocated it.

  15. Neil (SM) says:

    Oh c'mon! When does Yuhong Bao ever admit that he knows his post is not relevant to the current discussion?

    blogs.msdn.com/…/9453317.aspx

  16. w9x lover says:

    You can NOT assume a current directory is undeletable, because, as you said, w9x doesn't lock it. This is also true on wince, where there's no current dir at all, impossible to lock any dir by setting it to current. windows 8 could, in theory, revert back to w9x behaviour, thus, the locking is useless because it can't be trusted. Apps both have to make sure the dir is not locked and locked:

    1. If you delete a dir, you have to make sure it isn't locked.

    2. If you use a current dir, you have to make sure it exists.

  17. NT says:

    Just operate on full paths.

    I predict an upcoming post addressing path length limitations. ;)

  18. Alex says:

    @Dan Bugglin:

    In Linux you can delete any file whenever you like. Nothing to do with the current directory. This is great because you don't have to hunt down the damn program keeping you from disconnecting that USB flash drive you just wrote to, but has its own set of problems.

    Lets say your database backup program has a file open (lets say a dump file of 500GB) and you, the admin delete the file from the command line. You then try to copy another older dump file of say 400GB to the same file system and get a no space left on device error. Unfortunately the file system was only 700GB large, because dump files never get larger than 500GB (for some reason). You list the directory and it is empty. Funny situation … :) Actually happened to me at last job. Until the last program holding the file open closes it, it can still use it as it sees fit and the filesystem obviously can't really delete it yet.

  19. Søren Mors says:

    Process explorer (technet.microsoft.com/…/bb795533%28en-us%29.aspx ) can help you find out which program has a file open, which have helped me many times. It saves the bother of experimenting to find out which process keeps an open handle to a file, and goes straight to showing the culprit (handily, it can also kill the offending process). Wonderfull tool in many ways.

  20. Glaurung_quena says:

    Well, that explains something I've run into many times — I look inside a directory, discover there's nothing there I need, and then try to delete it only to discover that I can't because I was recently looking inside it. Very frustrating.

  21. Worf says:

    @Alex:

    Linux/Unix and derivatives implement two-phase deletes. Unlinking a file or directory removes the reference from the hard disk, but the space isn't reclaimed until the last handle is closed.

    Windows can do it as well (or used to – it supported POSIX after all), but Win32 didn't want to do this because people expect operations to fail if another process is using it (started from DOS networking – remember share.exe?).

    It's also why on non-journaled filesystems, if you delete a file and the system halts/reboots before the last program closed the handle, the file comes back during fsck with the inode number as filename. It was deleted, but the space wasn't reclaimed.

  22. 640k says:

    A system which prevents the user (admin) from overriding buggy apps are flawed.

    [Three words: "text file busy." As with unix, in Windows, the user (admin) can kill the buggy process. -Raymond]
  23. Andy says:

    Actually it's not NT as such that keeps the current directory open, but its Win32 layer. And it wouldn't be a problem if it opened it with FILE_SHARE_DELETE.

    @Dan Bugglin:

    Cygwin 1.7 before 1.7.5 avoided locking the current working directory with some unholy hackery that directly set the current directory file handle, which was opened with FILE_SHARE_DELETE. Unfortunately though that didn't work correctly under some circumstances on Vista and up (cygwin.com/…/msg00205.html). Hence, as you say, 1.7.6 simply set it to an invalid path out of the way instead, thus breaking mixed POSIX/Win32 scenarios. 1.7.7 went back to being a good Windows citizen and just calling SetCurrentDirectory, thus again making it impossible to delete a Cygwin process's current working directory.

    The upcoming Cygwin 1.7.8, however, has an even more evil hack that will once again set the current directory to an unlocked handle without using SetCurrentDirectory, this time hopefully without breaking things on Vista and up (cygwin.com/…/msg00342.html). Amazingly, the same user contributed the original problem report and the decidedly non-trivial solution.

    [Gotta admire the comment "I have no objections to us[ing] undocumented features, if they work." -Raymond]
  24. That's why I always delete directories *before* looking into them.

  25. Marquess says:

    “The link that Dheeraj has posted comes up as Malware according to ie9 beta.”

    Um, yeah, killing file handles is a dangerous act in unskilled hands (I think Raymond wrote about this before). As an administrator, I would be tempted to not only not include it in AppLocker's whitelist, but also blacklist it. And threaten anyone who considers using it with grievous bodily harm. Just to be sure.

    If you absolutely, positively have to get rid of a file handle, close the offending *process* instead.

    “That's why I always delete directories *before* looking into them.”

    Thank God for backups.

  26. Andy says:

    [Re:Raymond] Hacks like that obviously are a last resort if there's no other way to achieve POSIXness, and the Cygwin devs are only too aware that they might have to be reworked or disabled on future Windows versions.

    How about a proper solution to this, for example a flag on executables that causes SetCurrentDirectory not to lock the working directory? Essentially that would allow the programmer to say: I'm fine with my working directory disappearing.

    [And then your "weak current directory" app loads a DLL that is not compatible with weak current directories, and then…? Flagging the executable affects all DLLs. -Raymond]
  27. Klimax says:

    As for Unlocker – Neccesary evil as there are still buggy apps (or I use beta version…)

    And it is reported as malware due to used compression (AFAIK author did test notepad.exe with same result)

  28. Marquess says:

    "Hacks like that obviously are a last resort if there's no other way to achieve POSIXness,"

    Um… How about Interix?

  29. Bob says:

    I've never understood why Windows stubbornly doesn't allow deleting open files or directories. What's the problem?

    "Text file busy" is a completely different issue, and only prevents opening for writing. You can always delete the file and rewrite it (i.e. create a new inode with the same (or different) content and bind a name (filename) to the inode.

  30. Stig says:

    Never encountered this problem at all… you must do some really hacky coding or something… I do wonder too, why do you want to be able to delete folders that are in use either by applications that are running in them or files in them being opened in a program that does not allow editing the files it has opened?

  31. Andy says:

    @Marquess Interix runs in its own NT subsystem, whereas Cygwin runs within the Win32 subsystem. That's because Cygwin was originally developed for Win 9x, whereas Interix was always NT-based. The Interix approach means fewer headaches, but the Cygwin one yields more seamless Windows integration and the ability to mix POSIX and Windows APIs in the same program.

  32. Andre says:

    Also look for the windows index services accessing the directory.Process explorer is the best tool in any case.

  33. James Fuller says:

    > close the offending *process* instead.

    Processes don't always release their locked files when they terminate. Especially if they terminated abnormally.

  34. Joshua says:

    [Gotta admire the comment "I have no objections to us[ing] undocumented features, if they work." -Raymond]

    Unfortunately for everybody involved these guys are placed in a situation of works by undocumented function or doesn't work. It would be worth Microsoft's while to throw them a bone by exposing an API or two that does what they need. However that's not Raymond's decision.

  35. Andy says:

    [And then your "weak current directory" app loads a DLL that is not compatible with weak current directories, and then…? Flagging the executable affects all DLLs. -Raymond]

    Tough luck for me. If I don't know whether DLL functions I use are fine with a disappeared working directory, I can't use the flag.

    [Then I guess you can't use COM and the flag at the same time. -Raymond]
  36. Joshua says:

    Fun fact: all programs must be able to handle their current directory disappearing because network drives may dishonor locks (which turns out to be better than the alternative).

  37. Abort, Retry, Fail? says:

    The grandfather of disappearing media is floppy. It even predates fixed storage.

  38. Andy says:

    [Then I guess you can't use COM and the [weak working directory] flag at the same time. -Raymond]

    Can you be more specific on that? Under what circumstances would it fail, and how? Also, Joshua and "Abort, Retry, Fail?" make good points regarding network shares and removable media.

    In any case, many programs do not use COM, in particular Unix programs running on Cygwin, so that flag would still be very useful indeed and would allow getting rid of the hackery manipulating the current directory handle.

    [You CoCreateInstance an object, the server DLL is loaded, and that server DLL assumes strong current directories. And don't forget that you may be using COM despite not explicitly calling any COM functions. -Raymond]
  39. Andy says:

    Actually, a SetCurrentDirectoryEx() function that allows to specify whether to lock the directory would probably a better choice than the executable flag I'd proposed as it would stop users from setting the flag on programs that are not weak-current-directory-safe.

    [This assumes that you control all the code running in your process. (No COM, no plug-ins.) -Raymond]
  40. Joshua says:

    [This assumes that you control all the code running in your process. (No COM, no plug-ins.) -Raymond]

    The COM component that calls SetCurrentDirectory is playing with fire. You blogged about this one yourself in using global state to handle a local problem. The COM component that is not disappearing current directory safe is already vulnerable to certain race conditions (proof of this is non-trivial).

    I wouldn't put it past the Cygwin guys to patch ntdll in RAM to fix their current directory problem because if I did they'd probably do it and a better solution is warranted.

    {So you also fall into the camp that a program that is not 100% correct deserves to be 0% correct. -Raymond]
  41. Mike Dimmick says:

    @James Fuller:

    "Processes don't always release their locked files when they terminate. Especially if they terminated abnormally."

    Processes don't release anything, or at least don't have to. Windows tidies up after them. The kernel closes any handles remaining in the handle table when a process dies, and decrements the referenced object's reference count accordingly, whether by calling ExitProcess itself or someone else calling TerminateProcess.

    However, if another process has a handle open to the process, or a kernel component has added a reference to the object, it won't die completely and won't clean up. If you use ShellExecuteEx or CreateProcess, make sure you close the handles when you're done with them – particularly note that CreateProcess gives you a thread handle to the process's main thread, AND a process handle, and you need to close both of them.

  42. cgf says:

    [I wouldn't put it past the Cygwin guys to patch ntdll in RAM to fix their current directory problem because if I did they'd probably do it and a better solution is warranted. – Joshua]

    Actually this is something that the Cygwin guys would never consider.  We might use undocumented NT stuff from time to time but we sure aren't going to modify the behavior of the entire system in order to make Cygwin work better.  Especially when "better" in this case is the corner case of deleting the current working directory.

  43. Andy says:

    [[SetCurrentDirectoryEx] assumes that you control all the code running in your process. (No COM, no plug-ins.) -Raymond]

    Either that, or I'm happy to take the risk. A COM component or plugin that couldn't deal with a deleted current directory most likely also couldn't cope with network changes, disk removal, a full disk, security restrictions, or other problems that can befall I/O operations, so there's not an awful lot more to break here. Crucially, existing programs would be unaffected, and it would be up to program authors to consider whether they want to use that function. Cygwin sure would.

    [It's a calculated risk, but you have to watch out for the "0% or 100%" slippery slope. -Raymond]
  44. Corinna says:

    [Raymond, I'm puzzled why you deleted my yesterday's comment.  I didn't use any offensive language, did I?]

    [Discussing undocumented APIs is a good way to get a comment deleted. -Raymond]

Comments are closed.

Skip to main content