Why Linux Lets You Delete Running Files — And Windows Won’t
Why Linux Lets You Delete Running Files — And Windows Won’t
- Linux Kernel Removes strncpy After Six Years and 362 Patches
- Linux Kernel Drops 40-Year-Old AppleTalk Protocol — AI-Generated Patch Flood Was the Last Straw
- Apple’s Native Linux Container Tool Has Arrived — But Can It Really Replace Docker?
- 60% of MD5 Password Hashes Can Be Cracked in Under an Hour with a Single GPU
- Dirty Frag: Root Access on Every Major Linux Distribution — No Patch, No Warning
Why Linux Lets You Delete Running Files — And Windows Won’t
A look at the fundamental difference in file-locking philosophy between Windows and Linux, and the real-world pitfall that reveals exactly why this matters.
You are editing a Word document. Before saving, you try to move it to another folder. Windows stops you cold: “The file is being used by another program.” Most users have seen this message hundreds of times. Now switch to Linux, and the same scenario plays out differently — you can delete a file that is actively being used, rename it, or overwrite it, and the running process usually keeps humming along without so much as a warning. What is actually going on underneath?
The answer lies in a philosophical and architectural divergence between the two operating systems — one that dates back to their respective design origins and has profound implications for system administration, deployment practices, and software reliability.
Windows: Mandatory locking by default
When a Windows application opens a file, it typically calls the CreateFile() Win32 API and specifies a sharing mode. In many common cases — particularly for executables, libraries, and documents being written — applications request exclusive or restricted access. The kernel enforces this at the system level: other processes that attempt to open, move, rename, or delete a locked file are flatly refused.
This is called mandatory locking. The kernel itself prevents conflicting access, regardless of whether the competing process intends any harm. The system prioritises predictability and data integrity: if a file is open for writing, no one else touches it.
The mandatory locking model is precisely why Windows so frequently asks you to restart after software updates. When a running program holds a lock on a DLL or system file, the installer cannot replace it in place. The update is staged and only applied on the next boot, when no process holds a lock on those files.
The practical consequence is that system administrators on Windows must schedule maintenance windows and reboots for updates that would be handled seamlessly on Linux. It is a trade-off: simplicity and safety for the application developer at the cost of operational flexibility for the system operator.
Linux: Advisory locking and the inode model
Linux takes a fundamentally different approach. By default, file locks in Linux are advisory — they are cooperative agreements between processes, not kernel-enforced barriers. An application can request a lock via flock() or fcntl(), and well-behaved programs respect those locks. But the kernel will not physically prevent another process from opening or modifying the file if it does not bother to check for locks first.
More importantly, the Linux kernel’s internal accounting does not revolve around filenames. It revolves around inodes.
An inode is the kernel’s internal representation of a file object — it holds the file’s data block pointers, ownership, permissions, timestamps, and a reference count. A filename (a directory entry) is simply a pointer to an inode. When you open a file, the kernel gives your process a file descriptor that references the inode directly. The filename becomes irrelevant at that point.
This is why rm is technically a directory operation, not a data operation. When you delete a file, the kernel decrements the inode’s link count. Only when both the link count and the open file descriptor count reach zero does the kernel actually free the data blocks. Until then, any process with an open descriptor continues to read and write as if nothing happened.
Practical demonstration: deleting a running binary
Consider a running nginx web server. Its binary lives at /usr/sbin/nginx. If you execute:
rm /usr/sbin/nginx
The filename is removed from the filesystem. But the nginx worker processes, which opened that binary when they started, still hold file descriptors referencing the inode. The kernel keeps the inode alive. The website continues serving requests without interruption.
You can observe this state with:
lsof | grep deleted
This will show any process holding a file descriptor to a file whose directory entry has been removed — labelled with (deleted) to indicate the name is gone but the data is not. This is a powerful diagnostic command when a filesystem is unexpectedly full: a process may be writing to a log file that was “deleted” but whose space has not been reclaimed because the descriptor is still open.
How Linux enables zero-downtime deployments
This inode model is not merely a quirk — it is a deliberate design feature that underpins modern deployment practices. When you deploy a new version of a service on Linux:
The package manager or deployment tool writes the new binary to a temporary path, then atomically renames it over the old one (rename() is atomic at the filesystem level). The running process still holds its descriptor to the old inode, so it keeps executing the old code. A controlled restart — or a hot-reload signal — causes the service to exec the new binary from the filesystem. The old inode is released when the last descriptor closes, and its blocks are freed.
This is why Linux services can be updated without downtime, and why systemd’s daemon-reload followed by systemctl restart is a clean, safe operation: the old and new versions coexist briefly at the inode level without conflict.
The hidden danger: disk and memory diverge silently
This section documents a real failure pattern. The specifics illustrate a class of bug that is surprisingly easy to introduce in any long-running Linux service.
-
Apr 19
Developer refactors Python monitoring script. Uses
str | Noneunion syntax — a Python 3.10 feature. Server is running Python 3.9. Script is reloaded by systemd. - Apr 19–29 Script continues running normally. systemd did not restart the process; the new file was written to disk but the old process image, loaded into memory, kept executing. No errors. No alerts.
- Apr 30 Server reboots unexpectedly. systemd starts a fresh Python 3.9 process and reads the new code from disk for the first time.
-
Apr 30
Process crashes immediately:
TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'. The incompatibility had been silently masked for eleven days.
This incident illustrates one of the most deceptive failure modes in Linux systems administration. The developer assumed that saving a new file would cause the running service to reflect the change. It did not — and could not, because the process was executing code already resident in memory, mapped from the old inode.
The incompatibility was real and severe. But the Linux inode model insulated the running process from it completely — right up until a reboot forced a cold start from disk.
Comparing the two models
| Property | Windows | Linux |
|---|---|---|
| Default locking | Mandatory — kernel enforced | Advisory — application cooperative |
| Delete open file | Blocked by OS | Permitted; inode kept alive by open descriptors |
| Replace running binary | Blocked; requires reboot | Permitted; old process runs old inode until restarted |
| Update behaviour | Often requires restart to apply | Restart service to load new binary; OS reboot rarely needed |
| Disk/memory divergence | Not possible while file is locked | Possible and silent — a key operational hazard |
| Filesystem accounting unit | Filename (path) | Inode (reference-counted file object) |
| Mandatory locking support | Full, integral to design | Removed entirely in kernel 5.15 (2021) |
Linux did once have an optional mandatory locking feature, governed by CONFIG_MANDATORY_FILE_LOCKING. It was never reliable — kernel developers documented race conditions that made it unsound in practice. It was removed entirely in Linux 5.15, released in October 2021. Modern Linux is exclusively advisory-locking at the kernel level.
What the demand-paging model adds
One additional nuance is worth clarifying. It is a simplification to say a program’s code is “loaded into memory” at startup. Modern operating systems use demand paging: the binary is memory-mapped, and pages are loaded from disk only as they are needed. The file descriptor to the binary is held open throughout the process’s lifetime to support this.
On Linux, this means the kernel genuinely continues to read from the old inode’s data blocks on demand, even after the filename has been unlinked. The inode stays warm in the page cache. The running process never touches the new binary on disk unless it explicitly execs it.
Practical lessons for Linux operators
The freedom Linux grants — replacing files under running processes — comes with a corresponding responsibility. A few practices follow from this model:
After deploying new code to a long-running service, always issue an explicit restart or reload signal. Do not assume that writing a new file causes the running process to use it. Use lsof | grep deleted periodically to identify processes holding descriptors to deleted files — a common cause of surprising disk space exhaustion. And when a service behaves inconsistently after a deployment, check whether it was actually restarted: the process may have been running the prior version all along.
The Linux kernel does not protect you from your own changes. It gives you maximum flexibility and trusts you with the consequences. Windows makes the opposite trade-off: maximum protection, at the cost of operational agility.
Neither model is universally superior. For desktop environments where ordinary users work with documents and applications, mandatory locking prevents accidents. For servers and infrastructure, advisory locking with the inode model enables zero-downtime deployments that would require scheduled maintenance windows on Windows. Understanding which model you are working with — and what it silently permits — is foundational knowledge for anyone operating software in production.
