Windows CreateFile Sharing Semantics: Access, Rename, Delete, and Byte-Range Locks
Trace Windows sharing violations by separating access requests, reciprocal share permissions, delete/rename semantics, byte-range locks, and ACL authorization.
Windows CreateFileW has two independent access decisions that are easy to conflate. dwDesiredAccess says what the new handle wants to do. dwShareMode says which kinds of access future opens are allowed to request while this handle remains open. The system checks the new request against existing handles and checks existing requests against the new handle’s share flags. A sharing violation is therefore often a compatibility conflict between open handles, not evidence that the current user lacks an NTFS permission.
Access requested versus access allowed to others
An application that opens a file for reading might ask for GENERIC_READ while allowing subsequent readers and writers. A log collector might also include FILE_SHARE_DELETE if the log can be rotated by rename while the collector has it open. The share flags remain in force for the life of that handle; closing the handle removes that particular constraint. A zero dwShareMode is exclusive with respect to later read, write, and delete opens, and it can prevent even the same process from opening the file again through another handle.
The following example opens an existing file for reading, allowing other processes to read, write, or rename/delete it while the handle is open:
HANDLE file = CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (file == INVALID_HANDLE_VALUE) {
const DWORD error = GetLastError();
// Log the operation and error code; INVALID_HANDLE_VALUE is not NULL.
}
This is permissive sharing, not a locking protocol. If multiple writers need to update structured content safely, use an application-level coordination mechanism or an appropriate lock. Permitting concurrent writes does not make those writes atomic or preserve record boundaries.
Compatibility is reciprocal
Suppose process A opened a file requesting read access and sharing only reads. Process B’s later request for write access conflicts with A’s share policy, so the open fails. The reverse direction matters too: if B requests write access while allowing only reads, it conflicts with A’s existing read request, even if B itself is only trying to add a writer. This bidirectional rule explains why changing only the new caller’s share flags may not resolve a sharing violation; every existing handle’s requested access and allowed sharing must be compatible with the new pair.
When CreateFileW fails, it returns INVALID_HANDLE_VALUE, not NULL. Call GetLastError immediately, before another API call can overwrite the thread’s last-error value. ERROR_SHARING_VIOLATION points to a share-mode conflict; ERROR_ACCESS_DENIED may involve ACLs, integrity controls, read-only attributes, or a requested operation unsupported for the handle. Do not “fix” either error by granting broad permissions or disabling security checks before identifying the access path.
Delete and rename require their own sharing story
Windows treats delete access as relevant to both deleting and renaming a file. An open that omits FILE_SHARE_DELETE can block a later rename or delete request while the handle is open. This is why log rotation, update installers, antivirus scanners, and backup tools can interfere even when none of them is writing file contents at that moment. If an application is designed to tolerate replacement or rotation, it should choose the necessary sharing flags deliberately, then verify that subsequent reads still refer to the file identity it expects.
Allowing delete sharing is not a promise that the pathname will continue to refer to the same object. Another process can rename or replace the name after it is opened. If identity is security- or correctness-sensitive, retain and query the handle, compare stable file identity where the filesystem supports it, and avoid reopening a path and assuming it names the original object. Names and open handles have related but different lifecycles.
Creation disposition is another frequent source of accidental data loss. CREATE_ALWAYS replaces existing file contents when the open succeeds; it is not a harmless “open or create” synonym. CREATE_NEW is the create-only choice that fails if the name already exists. Use the disposition that matches the operation’s contract, and treat an overwrite as explicit destructive intent. OPEN_EXISTING is usually appropriate for readers and avoids silently creating an empty file after a typo in the path.
Sharing modes are not byte-range locks
Share flags govern whether another process may open the file with particular access classes. LockFileEx instead reserves a byte range through a file handle. It can request shared or exclusive access to a region; another process can still have an open handle to the same file while being blocked from the locked range. Byte-range locks are useful for cooperative record-oriented access, but they are not a universal file mutex: all participants must follow the protocol, mapped-file access is not blocked by these locks, and the lock coordinates only the specified range.
With overlapped I/O, a lock request can complete asynchronously. The OVERLAPPED structure identifies the starting offset, and the application must distinguish an immediate grant from a pending operation. Preserve that structure until the completion is observed, check its result, and call UnlockFileEx for the exact range before closing when the design requires explicit unlock. Closing the handle ultimately releases its locks, but relying only on abrupt handle loss can leave the application’s logical protocol unclear.
Opportunistic locks (oplocks) are another different mechanism. They let a client cache file data or metadata and receive a break notification when a conflicting operation arrives. Oplocks are cache-coherency coordination, not the same as access/share compatibility or application-level byte locks. Their break and acknowledgement lifecycle must be designed for the filesystem and network redirector in use.
A disciplined diagnostic for sharing violations
When an installer, editor, or service reports “file in use,” gather evidence before stopping processes:
- Record the exact path, operation, requested access, share flags, and creation disposition.
- Capture the returned Win32 error immediately and distinguish sharing conflict from access denial or file-not-found.
- Identify existing open handles using an approved diagnostic tool or application telemetry; a PID alone is not a durable identity because process IDs can be reused.
- Check whether a file rotation or rename is expected and whether any participant omitted
FILE_SHARE_DELETE. - Confirm that a filter driver, backup agent, indexer, or remote SMB client is not holding a handle that changes the compatibility result.
- Fix the narrowest owner: close an unnecessary handle, adjust a deliberate share policy, add a proper cooperative lock, or retry a transient operation with a bounded deadline.
Avoid repeatedly retrying in a tight loop. A conflicting handle may be long-lived or require user action; a tight retry can create CPU and log pressure without improving the outcome. Use backoff, expose the file and operation to the operator, and stop after a documented bound.
ACLs and share modes answer separate questions
The file’s security descriptor and the caller’s access token determine whether the requested access is authorized. The sharing check determines whether that authorized request is compatible with other open handles. Passing the share-mode check does not grant permission, and having an ACL allow GENERIC_WRITE does not override an incompatible existing handle. On a network path, SMB share permissions and server-side filesystem ACLs add further checks beyond the local handle model. Diagnosing remote access therefore requires evidence from both the client request and the server’s policy.
Open handles also have process-wide consequences. A background service can block a user’s rename without the user’s own process having opened the file. Conversely, a process that opened a handle with permissive sharing can allow others to proceed while it continues reading. Instrument long-lived handles with an owner, purpose, open time, and expected close condition so operations teams can tell a planned share from a leak.
Safe test cases
Use two small test processes to verify read/read compatibility, read/write compatibility under different share combinations, a delete/rename attempt with and without FILE_SHARE_DELETE, and a second open from the same process. Test the exact production filesystem and remote path if SMB is part of the workload. Add separate cases for CREATE_NEW, CREATE_ALWAYS, and OPEN_EXISTING, and assert the file contents after each disposition. For a byte-range lock, verify both a conflicting range and a non-overlapping range, then repeat with a memory-mapped view to confirm the documented limitation.
The reliable mental model is simple: requested access describes the caller; share flags describe what other opens may request; locks coordinate a range only when participants honor them; and ACLs decide authorization. Keeping these mechanisms separate turns “file in use” from guesswork into a diagnosable compatibility problem.
Related:
- NTFS Internals: the MFT, Journaling, and Alternate Data Streams
- NTFS USN Change Journal: Incremental File Tracking Without False Guarantees
Sources: