Nasty gotcha: STGM_READ | STGM_WRITE does not grant read/write access

You might think that if you want to get read/write access, you could pass STGM_READ | STGM_WRITE. You would be wrong. You have to pass STGM_READ­WRITE.

The three flags STGM_READ, STGM_WRITE, and STGM_READ­WRITE are mutually exclusive. If you try to combine them, you get a weird mess.

In particular, since the numerical value of STGM_READ is zero, passing STGM_READ | STGM_WRITE is numerically equivalent to passing STGM_WRITE, which grants write-only access.

The documentation for the STGM_* constants specifically says "It is not valid to use more than one element from a single group," and STGM_READ and STGM_WRITE belong to the Access group (as does STGM_READ­WRITE).

These values date back to the days of MS-DOS, where function 3Dh (Open File) passed an access mode in the AL register.

7 6 5 4 3 2 1 0
0 0 0 0 0 access

The bottom three bits specified the requested access (0 = read-only, 1 = write-only, 2 = read/write), and the remaining bits were reserved.

Later, when networking support was added in approximately MS-DOS 3.5, three more bits were pressed into service:

7 6 5 4 3 2 1 0
0 share
0 access

Sharing modes were 0 = compatibility mode, 1 = deny all, 2 = deny write, 3 = deny read, 4 = deny none.

These values were carried forward into Windows as flags to the Open­File function:

Value Description
Opens a file for reading only.
Opens a file for write access only.
Opens a file with read/write permissions.
Opens a file with compatibility mode, allows any process on a specified computer to open the file any number of times.
Opens a file with exclusive mode and denies both read/write ccess to other processes.
Opens a file and denies write access to other processes.
Opens a file and denies read access to other processes.
Opens a file without denying read or write access to other processes.

These flags were then carried forward into the STGM constants with the same numerical values.

Comments (10)
  1. laonianren says:

    This seemed like a strange design choice until I remembered that OLE2 predates Windows NT and 95.

    P.S. Congratulations on the completion of your first decade.  The blog is an impressive piece of work.

  2. Random832 says:

    This isn't the only function like this – open() is the same way, both on MSVC and on many Unix systems (including the original). I wonder if that's where DOS got it from.

    I'm somewhat confused that you say the bottom three bits are used, when the values only occupy two bits.

    [Values 3 through 7 were reserved for future use. -Raymond]
  3. Joshua says:

    And here begins the problem of so many programs opening files with delete locks when they don't need it or have any reason to care.

    @Random832: And on UNIX mode 3 means open handle to symbolic link (only on some kernels). The handle has a few uses, but read and write aren't among them.

  4. Gabe says:

    If only they had thought to make the names "STGM_READ_ONLY" and "STGM_WRITE_ONLY", then there might not be a problem. Few people would attempt to combine read-only with write-only.

  5. Dave says:

    Every time I see those STGM_* constants I pronounce them "stigmata". Which is actually pretty appropriate considering the pain they have put me through in the past.

  6. Simon Farnsworth says:

    @Random832 It goes back long before UNIX; AFAICT, tape-only OSes used a single bit for "read or write?"; when disks came along, to keep compatibility, they expanded to a 2 bit field, and made the third option "both read and write". That historical accident appears to have been pulled forward ever since.

  7. Faxmachinen says:

    @Simon: Interesting piece of knowledge. There's nothing stopping you from having both 0x0 and 0x2 mean "read" though (assuming 0x1 used to mean write), so I don't know what they were thinking.

  8. Joseph Koss says:

    This "problem" is caused by the values of constants being so easily buried in include files or the other similar methods (such as type libraries.) The values themselves indicate that they are not rooted in bit positions.

  9. cheong00 says:

    @Yuhong Bao: "approximately", so it is either on 3.x range or 4.x.

Comments are closed.