Why is it FILE_SHARE_READ and FILE_SHARE_WRITE anyway?

Raymond’s post about FILE_SHARE_* bits reminded me of the story about why the bits are FILE_SHARE_READ in the first place.

MS-DOS had the very same file sharing semantics as NT does (ok, NT adds FILE_SHARE_DELETE, more on that later). But on MS-DOS, the file sharing semantics were optional – you had to load in the share.com utility to enable them. This was because on a single tasking operating system, there was only ever going to be one application running, so the sharing semantics were considered optional. Unless you were running a file server, in which case Microsoft strongly suggested that you should load the utility.

On MS-DOS, the sharing mode was controlled by the three “sharing mode” bits. They legal values for “sharing mode” were:

            000 – Compatibility mode. Any process can open the file any number of times with this mode. It fails if the file’s opened in any other sharing mode.
001 – Deny All. Fails if the file has been opened in compatibility mode or for read or write access, even if by the current process
010 – Deny Write. Fails if the file has been opened in compatibility mode or for write access by any other process
011 – Deny Read – Fails if the file has been opened in compatibility mode or for read access by any other process.
100 – Deny None – Fails if the file has been opened in compatibility mode by any other process.

Coupled with the “sharing mode” bits is the four “access code” bits. There were only three values defined for them, Read, Write, and Both (Read/Write).

The original designers of the Win32 API set (in particular, the designer of the I/O subsystem) took one look at these permissions and threw up his hands in disgust. In his opinion, there are two huge problems with these definitions:

1) Because the sharing bits are defined as negatives, it’s extremely hard to understand what’s going to be allowed or denied. If you open a file for write access in deny read mode, what happens? What about deny write mode – Does it allow reading or not?

2) Because the default is “compatibility” mode, it means that by default most applications can’t ensure the integrity of their data. Instead of your data being secure by default, you need to take special actions to guarantee that nobody else messes with the data.

So the I/O subsystem designer proposed that we invert the semantics of the sharing mode bits. Instead of the sharing rights denying access, they GRANT access. Instead of the default access mask being to allow access, the default is to deny access. An application needs to explicitly decide that it wants to let others see its data while it’s manipulating the data.

This inversion neatly solves a huge set of problems that existed while running multiple MS-DOS applications – if one application was running; another application could corrupt the data underneath the first application.

We can easily explain FILE_SHARE_READ and FILE_SHARE_WRITE as being cleaner and safer versions of the DOS sharing functionality. But what about FILE_SHARE_DELETE? Where on earth did that access right come from? Well, it was added for Posix compatibility. Under the Posix subsystem, like on *nix, a file can be unlinked when it’s still opened. In addition, when you rename a file on NT, the rename operation opens the source file for delete access (a rename operation, after all is a creation of a new file in the target directory and a deletion of the source file).

But DOS applications don’t expect that files can be deleted (or renamed) out from under them, so we needed to have a mechanism in place to prevent the system from deleting (or renaming) files if the application cares about them. So that’s where the FILE_SHARE_DELETE access right comes from – it’s a flag that says to the system “It’s ok for someone else to rename this file while it’s running”. 

The NT loader takes advantage of this – when it opens DLL’s or programs for execution, it specifies FILE_SHARE_DELETE. That means that you can rename the executable of a currently running application (or DLL). This can come in handy when you want to drop in a new copy of a DLL that’s being used by a running application. I do this all the time when working on winmm.dll. Sine winmm.dll’s used by lots of processes in the system, including some that can’t be stopped, I can’t stop all the processes that reference the DLL, so instead, when I need to test a new copy of winmm, I rename winmm.dll to winmm.old, copy in a new copy of winmm.dll and reboot the machine.