Skip to content
FreeDOSDeep Dive Published Updated 8 min readViews unavailable

DOS Path Resolution: Current Drives, Per-Drive Directories, and Relative Names

Decode DOS path syntax precisely: distinguish C:FILE from C:\FILE, track each drive's directory, and test kernel resolution separately from shell search.

DOS paths look simple until an application changes drives, runs from a batch file, or receives a path from a different process. The key is that the current drive and the current directory for each drive are separate pieces of state. A drive letter followed by a colon does not necessarily mean the root, and a rooted path without a drive letter still depends on the current drive. Code that normalizes these cases incorrectly can open a different file than the operator intended.

This article focuses on classic DOS path resolution and the FreeDOS kernel’s Current Directory Structure (CDS), which stores drive-specific state used by DOS path services. It distinguishes filesystem resolution from command-shell PATH search and from tools such as SWSUBST that alter global drive mappings. Treat a resolved path as a description of DOS lookup at that moment, not as a security-grade, immutable file identity.

The four path forms that matter

Input Meaning Example interpretation
REPORT.TXT Relative to the current directory of the current drive If current drive is D: and its directory is \DATA, this names D:\DATA\REPORT.TXT.
C:REPORT.TXT Relative to the current directory recorded for C: It is not automatically C:\REPORT.TXT.
C:\REPORT.TXT Rooted on drive C: The current directory of C: does not change the root-based lookup.
\REPORT.TXT Rooted on the current drive If the current drive is D:, it names D:\REPORT.TXT.

The second form is the most common source of surprises. DOS keeps a current directory per drive, so C:REPORT.TXT can resolve differently from C:\REPORT.TXT even while D: is the selected drive. The drive-relative form asks DOS to use the remembered directory for the explicitly named drive; the rooted form explicitly starts at that drive’s root.

. and .. are directory-navigation components, not literal file names in normal path APIs. SUBDIR\ITEM.DAT extends the base directory, while ..\ITEM.DAT asks the resolver to move to its parent before looking up the file. A robust program must still handle failure: the target directory may not exist, media may have changed, a remote redirector may be unavailable, or a volume may report an invalid current-directory state.

Current drive and remembered directories are distinct

INT 21h/AH=19h reports the default drive as a zero-based value. INT 21h/AH=0Eh changes the default drive, but the selected letter is not the same thing as the per-drive directory. INT 21h/AH=3Bh changes a directory, and AH=47h can retrieve the directory associated with a requested drive. These services let an application make the state transition explicit rather than parsing shell prompt text.

For example, a command interpreter can remember a working directory on C:, switch to D:, and later return to C: without losing the former C: directory. DOS does not require every drive to have a valid directory at all times: a removable or network drive can be absent, and a drive may not exist in the current LASTDRIVE allocation. A query can fail even though the same letter worked earlier in the session.

The FreeDOS kernel stores drive-associated path state in CDS entries and consults it while resolving names. That helps explain why manually rewriting the CDS is unsafe: a CDS entry carries flags, drive ownership, and directory data, and network or joined/substituted drives do not necessarily behave like a plain local FAT volume. Use the owning redirector or mapping utility to change those relationships.

Shell commands are not path APIs

At a DOS prompt, typing C: changes the selected drive in the command interpreter. CD C:\TOOLS changes the directory on C:; it does not imply that every program opening a relative path will search PATH. The shell has command lookup and batch-language responsibilities that the kernel’s file-open service does not.

Applications should also distinguish path parsing from command-tail parsing. DOS receives a pointer to a path string for services such as open or change-directory; it does not split a shell command line into quoted arguments first. A program that accepts a path from its own command tail must parse that tail according to its interface, reject malformed or ambiguous input, and pass a properly terminated path to DOS. Do not copy a shell’s trimming or wildcard-expansion rules into a low-level file helper unless that helper intentionally implements them.

This distinction matters in launchers. INT 21h/AH=3Dh opens a specified path according to DOS resolution, but it does not search the shell’s executable PATH variable. Likewise, EXEC loads an explicitly named file; it is not a shell parser and does not automatically implement the interactive command processor’s lookup, quoting, built-ins, or redirection. A launcher that wants shell semantics must intentionally invoke a command interpreter and provide its command tail.

Use fully qualified paths where stable configuration is required. A batch file that expects a tool beside itself should derive that directory from a supported source or accept it as configuration; assuming the current directory equals the script’s directory is fragile. For a fixed system utility, C:\FREEDOS\BIN\TOOL.EXE is unambiguous only if the installation really uses that layout and drive letter.

Relative paths are especially risky across a child-process boundary. A parent can change its current drive or directories before invoking a child, and a shell can also establish a different startup directory than the caller expected. If the child needs a stable target, pass an explicit drive-qualified absolute path in the command tail or configure it independently. If the program must temporarily change a directory, capture and restore the old drive and each affected drive’s old directory; restoring only the selected drive does not restore per-drive CDS state.

Get canonical names without confusing them with identity

DOS-compatible kernels expose path-query services, including the TRUENAME convention and, in later APIs, current-directory calls. FreeDOS’s path resolver handles relative components, drive prefixes, device names, CDS state, and selected remote-drive behavior. Its output can be useful for displaying a normalized DOS path, but it does not prove the file is unchanged, exclusively owned, or safe from replacement between a check and an open.

There is no general DOS equivalent of a modern handle-based realpath guarantee across arbitrary redirectors. A network redirector can resolve a path remotely; a substituted drive can redirect it elsewhere; removable media can change; and another process can alter a directory entry after the query. If an operation is destructive, open the file using the intended API and verify the resulting handle/status rather than relying on a prior string comparison.

Case is also not a reliable identity test. Traditional DOS file services use case-insensitive 8.3 names, while optional long-filename layers can preserve case and apply different matching rules. Volume labels and device names introduce additional compatibility conventions. Keep control paths in portable 8.3 ASCII when a tool or boot phase may run without its long-name driver.

A safe path-resolution test matrix

In a disposable VM, create a small test tree and deliberately give each directory a different marker file. For example:

C:\ROOTMARK.TXT
C:\WORK\CWDMARK.TXT
D:\DATA\DWDATA.TXT

Then set C: to \WORK and D: to \DATA using normal DOS commands. From D:\DATA, compare lookups for CWDMARK.TXT, C:CWDMARK.TXT, C:\ROOTMARK.TXT, and \DWDATA.TXT. Log both the input and the canonical path returned by the target kernel, then open read-only and verify the marker contents. This catches drive-relative confusion that a test using only root paths will miss.

Also test absent media, an invalid drive, a drive beyond LASTDRIVE, . and .., a trailing separator, a directory name with an 8.3 alias, and a configured network/substituted drive. Run the same matrix through the actual shell and through the program’s DOS API calls. Different results can reveal command-shell search or parser behavior rather than a filesystem resolver defect.

Engineering rules for callers

  1. Preserve the drive prefix exactly when a path is drive-relative; do not silently insert a root separator.
  2. Use an explicit root when the operation must start at a known volume root.
  3. Treat current drive and per-drive directories as mutable shared process/system state, especially around child execution.
  4. Query errors are real states: handle missing drive, missing path, not-ready media, and redirector failures separately where useful.
  5. Avoid changing process directory globally just to make one helper resolve a path. Construct an explicit path instead, or save and restore the relevant state.
  6. Do not use a canonicalized string as authorization or proof that a later open refers to the same object.

Once the syntax is separated into drive selection, rooting, and relative components, the rules become predictable. The hardest bugs are usually not in the separators themselves; they come from assuming that all relative paths use one global working directory or that a shell’s command search is built into DOS file access.

Related:

Sources:

Comments