The curse of the current directory

Raymond Chen

Raymond

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.

Raymond Chen
Raymond Chen

Follow Raymond   

0 comments

Comments are closed.