Search code examples
winapifile-copyingcreatefile

What is the purpose of CopyFileEx's COPY_FILE_OPEN_SOURCE_FOR_WRITE?


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:

  • Since in both "exclusive" cases, the file is opened with ShareMode: Read, Delete (no share write), noone else can open it for writing anyways in both cases.
  • In both "shared" cases, if someone else already opened it for writing, the function internally falls back to Desired Access: Generic Read ... ShareMode: Read, Write, Delete anyways.

So,

  • the flag doesn't prevent anyone from opening the file with read access.
  • write access is already locked out by ShareMode Read only in the non-flag case
  • in case it doesn't work, it silently falls back to the non flag case.

So what'the'heck is this flag supposed to achieve? What is the Use Case?


Solution

  • 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.