It’s a bad idea to have a TEMP environment variable longer than about 130 characters
I've been working with the Win32 API for almost 20 years - literally since the very first Win32 APIs were written. Even after all that time, I'm occasionally surprised by the API behavior.
Earlier today I was investigating a build break that took out one of our partner build labs. Eventually I root caused it to an issue with (of all things) the GetTempName API.
Consider the following code (yeah, I don’t check for errors, <slaps wrist />):
#include "stdafx.h"
#include <string>
#include <iostream>
#include <Windows.h>
using namespace std;
const wchar_t longEnvironmentName[] =
L"c:\\users\\larry\\verylongdirectory\\withaverylongsubdirectory"
L"\\andanotherlongsubdirectory\\thatisstilldeeper\\withstilldeeper"
L"\\andlonger\\untilyoustarttorunoutofpatience\\butstillneedtobelonger"
L"\\untilitfinallygetslongerthanabout130characters";
int _tmain(int argc, _TCHAR* argv[])
{
wchar_t environmentBuffer[ MAX_PATH ];
wchar_t tempPath[ MAX_PATH ];
SetEnvironmentVariable(L"TEMP", longEnvironmentName);
SetEnvironmentVariable(L"TMP", longEnvironmentName);
GetEnvironmentVariable(L"TEMP", environmentBuffer, _countof(environmentBuffer));
wcout << L"Temp environment variable is: " << environmentBuffer << " length: " << wcslen(environmentBuffer) << endl;
GetTempPath(_countof(tempPath), tempPath);
wcout << L"Temp path: " << tempPath<< " length: " << wcslen(tempPath) << endl;
return 0;
}
When I ran this program, I got the following output:
Temp environment variable is: c:\users\larry\verylongdirectory\withaverylongsubdirectory\andanotherlongsubdirectory\thatisstilldeeper\withstilldeeper\andlonger\ untilyoustarttorunoutofpatience\butstillneedtobelonger\untilitfinallygetslongerthanabout130characters length: 231 Temp path: C:\Users\larry\ length: 15
So what’s going on? Why did GetTempPath return a pointer to my profile directory and not the (admittedly long) TEMP environment variable?
There’s a bunch of stuff here. First off, let’s consider the documentation for GetTempPath:
The GetTempPath function checks for the existence of environment variables in the following order and uses the first path found:
- The path specified by the TMP environment variable.
- The path specified by the TEMP environment variable.
- The path specified by the USERPROFILE environment variable.
- The Windows directory.
So that explains where the c:\Users\larry came from – something must have gone wrong retrieving the “TMP” and “TEMP” environment variables so it fell back to step 3[1]. But what could have happened? We know that at least the TEMP environment variable was correctly set, we retrieved it in our test application. This was where I got surprised.
It turns out that under the covers (at least on Win7), the function which retrieves the TEMP and TMP environment variables uses a UNICODE_STRING structure to initialize the string. And, for whatever reason, they set MaximumLength to MAX_PATH+1. If I look at the documentation for UNICODE_STRING, we find:
MaximumLength
Specifies the total size, in bytes, of memory allocated for Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory.
So the function expects at most 261 bytes, or about 130 characters. I often see behaviors like this in the "A" version of system APIs, but in this case both the "A" and "W" version of the API had the same unexpected behavior.
The moral of the story: If you set your TEMP environment variable to something longer than 130 characters or so, GetTempPath will return your USERPROFILE. Which means that you may unexpectedly find temporary files scribbled all over your profile directory.
The fix was to replace the calls to GetTempPath with direct calls to GetEnvironmentVariable - it doesn't have the same restriction.
[1] Note that the 4th step is the Windows directory. You can tell that this API has been around for a while because apparently the API designers thought it was a good idea to put temporary files in the windows directory.
EDIT: Significantly revised to improve readability - I'm rusty at this.
Comments
Anonymous
October 19, 2010
It's also a bad idea to use long file names in your TEMP environment variable. blogs.msdn.com/331750.aspxAnonymous
October 21, 2010
Interesting. I must admit the limitations that Windows imposes on path length, even on the bright and shiny new Windows 7 64-bit or Server 2008 R2, is somewhat maddening. I can have terabytes of RAM, but MAX_PATH is still 255. Pft. Even the Shell won't let you interact with really long path names. While I admit it might be a hard problem to solve, I would have thought that the move to 64-bit would be a good starting place. Maybe change MAX_PATH for 64-bit applications (which, I would hope, would be written to handle arbitrary path lengths correctly) while keeping the old value for WOW64 apps. But who knows, there's probably a dozen reasons that wouldn't work. One thing that caught my eye was "I root caused it to an issue". Is the verb phrase "root caused" some Microspeak that slipped in, or is it just something you use? I see that Raymond read your post -- should I expect a Microspeak post from him on "root causing" in the future? :)Anonymous
October 22, 2010
@Larry Great to see you posting again. @Nick Root caused would be shorthand for "I did a root cause analysis and found ..." I've seen it used at non-Microsoft companies.Anonymous
October 28, 2010
And so, what is the internal process to fix this bug? When will this limit be documented on MSDN? When will the labmda user get a fixed GetTempPath on his system?Anonymous
October 28, 2010
@Dolmen: It's not clear that there's a bug to be fixed. Just because I think something is a bug doesn't mean that it isn't by design. And fixing this is almost certainly going to cause appcompat regressions (the behavior has been there for a very long time). Even if it were to be fixed, it won't be fixed until the next version of Windows. If you feel that this needs to be fixed sooner, feel free to open an incident report with customer support services and they'll triage it appropriately.Anonymous
October 31, 2010
I doubt that "fixing" the API (presumably so it allocates MAX_PATH characters instead of bytes) would cause many regressions. How many Unicode programs out there could be relying on having 130-character TEMP paths so that their temp files end up in %USERPROFILE%? I would also argue that using the WINDOWS directory is never the right thing, and should be "fixed".Anonymous
November 29, 2010
> what is the internal process to fix this bug?
- Somebody (person A) notices the bug.
- Somebody (person B) files a bug report.
- Somebody (person C) decides that the bug should be fixed (or not.)
- Somebody (person D) fixes the bug.
- Somebody (person E) verifies the fix (or not.) For this bug, step 3. has been completed... awaiting step 4.
- Anonymous
January 10, 2011
Hi, Larry. Sorry fo off-topic. I tried the Windows 7 audio feature that you called "Call control" in a video interview. I start playing music with Windows Media Player, then get a Skype call from my wife and when I answer, Media Player is neither faded out nor paused. Should the feature work or it has been abandoned?