Win32 CopyFileEx
and CopyFile2
both have an option flag
COPY_FILE_OPEN_SOURCE_FOR_WRITE
where the docs state:
COPY_FILE_OPEN_SOURCE_FOR_WRITE 0x00000004
The file is copied and the original file is opened for write access.
Now, this sounds straightforward, but when I use this flag and try to copy a file that is already opened with "write lock" by someone else, that is no FILE_SHARE_WRITE, but FILE_SHARE_READ, the file is still being copied!
Here is what ProcMon shows when calling this function. Note that these CreateFile events are from a single call to the CopyFileEx function and I omitted the other CreateFile call to the target file:
(sorry for the cinemascope data)
# Without COPY_FILE_OPEN_SOURCE_FOR_WRITE
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a, OpenResult: Opened
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Synchronous IO Non-Alert, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a, OpenResult: Opened
# With COPY_FILE_OPEN_SOURCE_FOR_WRITE
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read/Write, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a, OpenResult: Opened
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read/Write, Disposition: Open, Options: Sequential Access, Synchronous IO Non-Alert, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a, OpenResult: Opened
# Without, but file already opened prior by someone else as GENERIC_READ+WRITE + FILE_SHARE_READ (deny write)
CreateFile D:\tmp\cpy.txt SHARING VIOLATION Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Synchronous IO Non-Alert, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
# *With*, but file already opened prior by someone else as GENERIC_READ+WRITE + FILE_SHARE_READ (deny write)
CreateFile D:\tmp\cpy.txt SHARING VIOLATION Desired Access: Generic Read/Write, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a
CreateFile D:\tmp\cpy.txt SHARING VIOLATION Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Delete, AllocationSize: n/a
CreateFile D:\tmp\cpy.txt SHARING VIOLATION Desired Access: Generic Read/Write, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
CreateFile D:\tmp\cpy.txt SUCCESS Desired Access: Generic Read, Disposition: Open, Options: Sequential Access, Synchronous IO Non-Alert, Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
As we can see, if noone has access to the file before hand, it is indeed opened with Generic Read/Write
access.
However, if you look at the non-flag case and especially at the case where someone else already opened the file for writing, I fail to see what this flag actually gains:
ShareMode: Read, Delete
(no share write), noone else can open it for writing anyways in both cases.Desired Access: Generic Read ... ShareMode: Read, Write, Delete
anyways.So,
So what'the'heck is this flag supposed to achieve? What is the Use Case?
Courtesy of Old New Thing Comment Power:
Malcolm Smith
It’s to support MoveFileWithProgress, specifically when the file being moved is moving across volumes and is the target of a shortcut. ... Once a file is “moved” to a new volume, this information is removed from the source of the move and applied to the target of the move, then the source is deleted. Removing from the source is a modification. The flag is just due to how the code is structured, where copy opens the handles.
... the MoveFileWithProgress shenanigans will use this flag to have CopyFileEx
try to open the source file in a writable way so that its callback handler can potentially do some modifying operations on the source handle once it gets to the finished state?
So the general use case could be phrased: Use this flag if your callback handler wants to modify something on the source file. But there’s no guarantee (that is, no failure on the side CopyFileEx
itself) that the source file will be opened in write mode, it’s just best effort.
... When the code was written, it performed this update in the callback handler, so depended on copy to open handles for write. It doesn’t do that anymore, but that’s still the reason the flag was added.
As far as the way it “tries” to open the handles for write, see also MOVEFILE_FAIL_IF_NOT_TRACKABLE. Since the case for this is very narrow, most of the time write access isn’t required, so downgrading to read only is completely benign. ...
So the remaining public use for this is exactly as you describe: you can update the source in a callback handler, but must be prepared to detect access denied conditions and perform appropriate error handling at the time.