Windows 11 and `NUL`

We had a bug report complaining that we were leaving a bunch of NUL.o files on disk. The user had a Windows 11 machine, and we had never had complaints before.

In Windows, NUL is a special file, treated as a reserved name for a special device file that discards all data written to it. This behavior dates back to MS-DOS. An application can “create” and “write” to a NUL file (or variations like NUL.txt, NUL.o, etc.), but in reality, the file is never actually created on disk, regardless of the underlying filesystem.

Many applications, including ours, rely on this fact when we need a command that requires an output file to dump its output “nowhere.”

Let’s create a simple Python script to test this:

import os
from pathlib import Path
from tempfile import TemporaryDirectory


def create_file(path: Path):
    try:
        with open(path, "w") as f:
            f.write("1234567890\n")
    except OSError as e:
        print(f"Error creating file '{nul}': {e}")


def list_files(path: Path):
    print("\nList of files:")
    for file in path.iterdir():
        stat = os.stat(file)
        print(f"\t{file} {stat.st_size} bytes")


with TemporaryDirectory(delete=False) as tempdir:
    tempdir = Path(tempdir)

    nuls = ["nul", "NUL", "NUL.o", "NUL.o:", "NUL:.o", "\\.\\NUL"]
    for nul in nuls:
        path = nul if nul.startswith("\\") else tempdir / nul
        create_file(path)

    list_files(tempdir)

Windows 10

If we run this on Windows 10, we get:

> python .\nulpy.py

List of files:

So, no errors, and no files persisted, as we would expect.

Note that I have also added a device path: \.\NUL, which is a “device path” pointing to the NUL device.

Windows 11

In Windows 11, however, the behavior has changed:

PS C:\Users\Alejandro\nul-py> python .\nulpy.py
Error creating file 'NUL.o:': [Errno 22] Invalid argument: 'C:\\Users\\ALEJAN~1\\AppData\\Local\\Temp\\tmpwa3xxhbs\\NUL.o:'

List of files:
        C:\Users\ALEJAN~1\AppData\Local\Temp\tmpwa3xxhbs\NUL 0 bytes
        C:\Users\ALEJAN~1\AppData\Local\Temp\tmpwa3xxhbs\NUL.o 12 bytes

First, NUL.o: is not accepted and causes an error. Second, NUL and NUL.o files are created! More specifically, NUL.o is created by opening, well, NUL.o. It has a size of 12 bytes, which means the bytes actually landed on disk.

However, and quite surprisingly, the empty NUL is not created by any of the first two entries. It is created when writing to NUL:.o. In this case, the data is thrown away 🤷🏿.

In our case, using the device path did the trick, since the tool (a compiler, as you may have guessed) luckily did not try to add .o when specifying it, because opening \.\NUL.o is an error.

Microsoft has documented reserved filenames and device paths in Windows (Naming Files, Paths, and Namespaces), but I have not been able to find any explicit documentation describing this change in behavior.