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.