Since more than 40 years, `c:tmp.txt` is a valid file path on Windows (because of MS-DOS 2.x)
Posted by jpluimers on 2026/07/22
Some are surprised but because of MS-DOS 2.x compatibility:
`
c:tmp.txt` is a valid file path on Windows
Since each drive letter remembers the current directory and that memory bit is not thread-local nor process-local, so relying on it is bad practice.
The above is a quote from [Wayback/Archive] Oleksii Holub 🇺🇦 on Twitter: “So I was trying to understand the difference between <code>Path.IsPathRooted(...)</code> and <code>Path.IsPathFullyQualified(...)</code> and learned that apparently <code>c:tmp.txt</code> is a valid file path on Windows 🤯” which has an image without alt-text on a long bit of Microsoft documentation which I will further quote below.
Many people don’t know this, so here is a list of variations on relative paths:
| path | meaning |
|---|---|
C:temp.txt |
relative to the current directory on the specific drive C: * |
temp.txt |
relative to the current directory on the current drive * |
\temp.txt |
relative to the current drive inside its root directory |
Paths can only be relative on drives and even the drive letter can be relative.
Note that for * you can also prefix
temp.txtwith directory names, for instance:
path meaning .\the current directory ..\the parent directory of the current directory zzz\a specific directory zzzinside the current directory
This means there are no relative paths on:
- UNC paths (often called network paths)
- Namespace paths (either
\\?\X:\based whereX:is a drive, or\\.\DeviceName\based though the latter are not available fromcmd.exe)
From a historic perspective, this is very logical as back in the days, these were the main APIs to change drive and current directory, and to retrieve those values (all from the famous Ralf Brown’s Interrupt List – Wikipedia):
- [Wayback/Archive] Ralf Brown’s Interrupt List – HTML Version: Ralf Brown’s Interrupt List Indexed HTML Version – Release 61
- [Wayback/Archive] int 21
- [Wayback/Archive] Int 21/AH=0Eh: DOS 1+ – SELECT DEFAULT DRIVE
- [Wayback/Archive] Int 21/AH=19h: DOS 1+ – GET CURRENT DEFAULT DRIVE
- [Wayback/Archive] Int 21/AH=3Bh: DOS 2+ – CHDIR – SET CURRENT DIRECTORY
If new directory name includes a drive letter, the default drive is not changed, only the current directory on that drive. Changing the current directory also changes the directory in which FCB file calls operate.
- [Wayback/Archive] Int 21/AH=47h: DOS 2+ – CWD – GET CURRENT DIRECTORY
The returned path does not include a drive or the initial backslash.
- [Wayback/Archive] int 21
This has resulted in some run-time libraries still distinguishing between ChDrive (or ChDrv) and ChDir.
Related
- [Wayback/Archive] Maximum Path Length Limitation – Win32 apps | Microsoft Learn
- [Wayback/Archive] File path formats on Windows systems | Microsoft Learn
- Path (computing) – Wikipedia
- [Wayback/Archive] The weird world of Windows file paths | Fileside (although I think the “Device Path” statements might not be accurate)
- [Wayback/Archive] ChDir statement (VBA) | Microsoft Learn
- [Wayback/Archive] ChDrive statement (VBA) | Microsoft Learn
- [Wayback/Archive] FileSystem.ChDir(String) Method (Microsoft.VisualBasic) | Microsoft Learn
- [Wayback/Archive] FileSystem.ChDrive Method (Microsoft.VisualBasic) | Microsoft Learn
- [Wayback/Archive] FileSystem.CurDir Method (Microsoft.VisualBasic) | Microsoft Learn (which returns both drive and directory)
- [Wayback/Archive] chdir | Microsoft Learn (DOS/cmd.exe)
- [Wayback/Archive] chdir | Microsoft Learn (C++)
- [Wayback/Archive] _chdir, _wchdir | Microsoft Learn
- [Wayback/Archive] _chdrive | Microsoft Learn
- [Wayback/Archive] _getcwd, _wgetcwd | Microsoft Learn
- [Wayback/Archive] _getdrive | Microsoft Learn
Old-new-Thing entries:
- [Wayback/Archive] The curse of the current directory – The Old New Thing
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.)
- [Wayback/Archive] Why does each drive have its own current directory? – The Old New Thing
- [Wayback/Archive] What are these strange =C: environment variables? – The Old New Thing
- [Wayback/Archive] Why can’t you set the command prompt’s current directory to a UNC? – The Old New Thing
- [Wayback/Archive] Why is a drive letter permitted in front of UNC paths (sometimes)? – The Old New Thing
In order to retain compatibility with programs that provided this sort of “unwanted help”, the designers of the networking support in MS-DOS decided to allow the strange syntax
C:\\server\share\directoryand treat it as if the drive letter simply weren’t there. Some (but not all) of this quirk of path parsing persists today. - [Wayback/Archive] Why does IsPathRelative return FALSE for paths that are drive-relative? – The Old New Thing
Here’s a table of possibilities.Example Classification IsPathRelative? \\Server\Share\Dir\File.txtUNC absolute No C:\Dir\File.txtDrive absolute No C:Dir\File.txtDrive relative No \Dir\File.txtRooted No Dir\File.txtRelative Yes The first two rows and the last row are not controversial. The third and fourth rows, on the other hand, are problematic. They live in this shadowy world, half-absolute and half-relative. - [Wayback/Archive] What are these dire multithreading consequences that the GetFullPathName documentation is trying to warn me about? – The Old New Thing
What it’s trying to say is that the meaning of a relative path depends on the current value of the current directory. The value of the current directory can be changed by any thread at any time, so make sure that you understand that the result of the
GetFullPathNamefunction is a “moment in time” calculation. Resolving the same relative path in consecutive calls to theGetFullPathNamefunction could result in different results if the current directory changed in between.
Notes
Because cmd.exe stores the current directory for each drive in a separate environment variable, but most Windows API functions do not, you might want to do that yourself. This is exactly what the C++ RTL _chdir does in addition to the calling the SetCurrentDirectory functionality as per [Wayback/Archive] _chdrive / SetCurrentDirectory resetting the current directory on other drives?
I always forget the CRT source is available. :)
The most interesting source is that of _chdir, which shows it is doing
extra bookkeeping beyond whatSetCurrentDirectorydoes, as I suspected
(although the way the extra info is stored and the fact that other
Win32 APIs use it automatically did surprise me).From
chdir.c:
* If the new current directory path is NOT a UNC path, we must
* update the OS environment variable specifying the current
* directory for what is now current drive. To do this, get the
* full current directory, build the environment variable string
* and call SetEnvironmentVariable(). We need to do this because
* SetCurrentDirectory does not (i.e., does not update the
* current-directory-on-drive environment variables) and other
* functions (fullpath, spawn, etc) need them to be set.
*
* If associated with a 'drive', the current directory should
* have the form of the example below:
*
* D:\nt\private\mytests
*
* so that the environment variable should be of the form:
*
* =D:=D:\nt\private\mytests
Quotes
Few people seem to know [Wayback/Archive] Changing the Current Directory – Win32 apps | Microsoft Learn
Although each process can have only one current directory, if the application switches volumes by using the SetCurrentDirectory function, the system remembers the last current path for each volume (drive letter). This behavior will manifest itself only when specifying a drive letter without a fully qualified path when changing the current directory point of reference to a different volume. This applies to either Get or Set operations.
Which means that unlike cmd.exe (which uses Environment variables to keep of the current directory for each drive), there is no need for PowerShell to do the same. So dir env: does not show these environment variables: [Wayback/Archive] PowerTip: Use Windows PowerShell to display all Environment variables – Scripting Blog
Answer: You can do this in one line using the env: PowerShell drive to display all of the currently set Environment variables.dir env:
In addition, the current directory is a proces wide thing, and can cause thread-safety issues as per [Wayback/Archive] SetCurrentDirectory function (winbase.h) – Win32 apps | Microsoft Learn:
The current directory is shared by all threads of the process: If one thread changes the current directory, it affects all threads in the process. Multithreaded applications and shared library code should avoid calling the
SetCurrentDirectoryfunction due to the risk of affecting relative path calculations being performed by other threads. Conversely, multithreaded applications and shared library code should avoid using relative paths so that they are unaffected by changes to the current directory performed by other threads.
Quoted bit of information of [Wayback/Archive] Naming Files, Paths, and Namespaces – Win32 apps | Microsoft Learn:
Fully Qualified vs. Relative Paths
For Windows API functions that manipulate files, file names can often be relative to the current directory, while some APIs require a fully qualified path. A file name is relative to the current directory if it does not begin with one of the following:
- A UNC name of any format, which always start with two backslash characters (“
\\“). For more information, see the next section.- A disk designator with a backslash, for example “
C:\” or “d:\“.- A single backslash, for example, “
\directory” or “\file.txt“. This is also referred to as an absolute path.If a file name begins with only a disk designator but not the backslash after the colon, it is interpreted as a relative path to the current directory on the drive with the specified letter. Note that the current directory may or may not be the root directory depending on what it was set to during the most recent “change directory” operation on that disk. Examples of this format are as follows:
- “C:tmp.txt” refers to a file named “tmp.txt” in the current directory on drive C.
- “C:tempdir\tmp.txt” refers to a file in a subdirectory to the current directory on drive C.
A path is also said to be relative if it contains “double-dots”; that is, two periods together in one component of the path. This special specifier is used to denote the directory above the current directory, otherwise known as the “parent directory”. Examples of this format are as follows:
- “..\tmp.txt” specifies a file named tmp.txt located in the parent of the current directory.
- “..\..\tmp.txt” specifies a file that is two directories above the current directory.
- “..\tempdir\tmp.txt” specifies a file named tmp.txt located in a directory named tempdir that is a peer directory to the current directory.
Relative paths can combine both example types, for example “C:..\tmp.txt”. This is useful because, although the system keeps track of the current drive along with the current directory of that drive, it also keeps track of the current directories in each of the different drive letters (if your system has more than one), regardless of which drive designator is set as the current drive.
Delphi?
Don’t count on the Delphi RTL to get this right: [Wayback/Archive] Meik Tranel on Twitter: “@jpluimers @Tyrrrz Now the question is – is TPath.IsPathRooted following this as well or does it actually check what i’m referring to? Because there is no FullyQualified check in System.IOUtils.TPath… “
Queries
- [Wayback/Archive] Why does each drive have its own current directory – Google Search
- [Wayback/Archive] windows api set current drive – Google Search
- [Wayback/Archive] windows api chdrive – Google Search
- [Wayback/Archive] powershell chdrive – Google Search
- [Wayback/Archive] “ChDrive” – Google Search
- [Wayback/Archive] “ChDir” – Google Search
- [Wayback/Archive] “ChDrv” drive – Google Search
–jeroen






Leave a comment