http://bugs.winehq.org/show_bug.cgi?id=58155
Bug ID: 58155 Summary: Use space-saving APFS clones instead of copying file data blocks to each WINEPREFIX on macOS Product: Packaging Version: unspecified Hardware: x86-64 OS: MacOS Status: UNCONFIRMED Severity: normal Priority: P2 Component: wine-packages Assignee: wine-bugs@winehq.org Reporter: cemer99797@isorax.com CC: dimesio@earthlink.net
When a WINEPREFIX is created using Wine on macOS, files from the Wine .app are copied using actual copying of data blocks, instead of the space-saving Copy-on-Write cloning mechanism of APFS (Apple File System), which only references the already existing data blocks.
When using Gcenx's macOS package builds, the data blocks of files are copied (from I believe Wine.app/Contents/Resources/wine/lib/wine/) to $WINEPREFIX/drive_c/windows/. This wastes over 300 MB of space for each WINEPREFIX created, which can add up, especially as it's recommended to install each Windows app in its own WINEPREFIX. They should instead use APFS CoW clones (similar to Linux btrfs reflinks).
Solution: use APFS Copy-on-Write when creating WINEPREFIXes, so it is faster and saves space
How?: using the macOS terminal command `cp -c` uses CoW
see macOS's man pages for `cp(1)` and `clonefile(2)`
macOS 10.14 Mojave man pages online: https://www.unix.com/man_page/mojave/1/cp https://www.unix.com/man_page/mojave/2/clonefile/
A Stack Exchange answer mentioning that macOS's "`cp -c` is equivalent to [Linux] `cp --reflink=always` (not `auto`), and will fail when copy-on-write is not possible": https://unix.stackexchange.com/a/504330
APFS was released for macOS devices on September 25, 2017, with the release of macOS 10.13 High Sierra. (https://en.wikipedia.org/wiki/Apple_File_System#History)
Gcenx's Wine 10.0 and later package releases for macOS (https://github.com/Gcenx/macOS_Wine_builds/releases) only support macOS 10.15 Catalina and later. (macOS 10.12 Sierra though macOS 10.14 Mojave can be installed via MacPorts).
How to test that Wine is not currently using APFS CoW clones: open Disk Utility before creating a new WINEPREFIX, click on your Container disk and look at the free space. Now create a new WINEPREFIX, the free space will decrease by about 300 MB. Delete the WINEPREFIX and empty the trash, watch the free space grow back
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #1 from ToastyBug cemer99797@isorax.com --- Another option might be using symlinks to the files by pointing them to Wine.app/Contents/Resources/wine/lib/wine/, but that might introduce other issues (for example if a Windows program modifies files in its $WINEPREFIX/drive_c/windows/)
http://bugs.winehq.org/show_bug.cgi?id=58155
Austin English austinenglish@gmail.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC|dimesio@earthlink.net | Component|wine-packages |-unknown Product|Packaging |Wine Version|unspecified |10.6
--- Comment #2 from Austin English austinenglish@gmail.com --- Not a packaging bug
http://bugs.winehq.org/show_bug.cgi?id=58155
Brendan Shanks bshanks@codeweavers.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |bshanks@codeweavers.com
--- Comment #3 from Brendan Shanks bshanks@codeweavers.com --- This would be a good improvement to have, but it's unfortunately not straightforward to implement. Windows also has copy-on-write support in the ReFS file system, and there are APIs to use it (see https://learn.microsoft.com/en-us/windows-server/storage/refs/block-cloning) which can clone a specified number of bytes in one file to another existing file. (Linux also has similar reflink APIs).
macOS only has clonefile(2) which copies an entire file to a new filename, and no "specific bytes" or even "preexisting file" API. This is not a good match with the Windows CopyFile() APIs (and the fairly new NT kernel syscall implementing it), which work by first creating the new file, setting attributes, and then copying the bytes in.
I filed a feedback with Apple 2 years ago asking for a system call that can clone parts of files into existing files, without that I don't think this could ever be implemented in upstream Wine. (It might be possible in a downstream like CrossOver though).
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #4 from Ken Sharp imwellcushtymelike@gmail.com --- CoW has been available for years. This seems like a waste of developer time.
In Linux, you can just use duperemove on a BTRFS system (as well as compression). Can't you do that on Apple?
http://bugs.winehq.org/show_bug.cgi?id=58155
Austin English austinenglish@gmail.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |austinenglish@gmail.com
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #5 from ToastyBug cemer99797@isorax.com --- (In reply to Brendan Shanks from comment #3)
This would be a good improvement to have, but it's unfortunately not straightforward to implement. Windows also has copy-on-write support in the ReFS file system, and there are APIs to use it (see https://learn.microsoft.com/en-us/windows-server/storage/refs/block-cloning) which can clone a specified number of bytes in one file to another existing file. (Linux also has similar reflink APIs).
macOS only has clonefile(2) which copies an entire file to a new filename, and no "specific bytes" or even "preexisting file" API. This is not a good match with the Windows CopyFile() APIs (and the fairly new NT kernel syscall implementing it), which work by first creating the new file, setting attributes, and then copying the bytes in.
I filed a feedback with Apple 2 years ago asking for a system call that can clone parts of files into existing files, without that I don't think this could ever be implemented in upstream Wine. (It might be possible in a downstream like CrossOver though).
Why should Wine need to clone parts of files into existing files? When you first run Wine (with no WINEPREFIX created), Wine will create an entirely new WINEPREFIX and duplicate (copy) fresh files from Wine.app/Contents/Resources/wine/lib/wine/ to $WINEPREFIX/drive_c/windows/. In this case, APFS copy-on-write is exactly what is needed to make all those files "references" to the originals, without actually copying data blocks on disk.
macOS uses this APFS copy-on-write system by default when duplicating files with the macOS Finder, and when using the Terminal `cp` command with the `-c` flag.
GNU coreutils has implemented APFS copy-on-write for macOS by default since release 9.1 (2022-04-15) [stable]:
On macOS, cp creates a copy-on-write clone if source and destination are regular files on the same APFS file system, the destination does not already exist, and cp is preserving mode and timestamps (e.g., 'cp -p', 'cp -a').
(from the GNU coreutils changelog: https://github.com/coreutils/coreutils/blob/master/NEWS, search for the text "copy-on-write" for more)
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #6 from ToastyBug cemer99797@isorax.com --- (In reply to Ken Sharp from comment #4)
CoW has been available for years. This seems like a waste of developer time.
In Linux, you can just use duperemove on a BTRFS system (as well as compression). Can't you do that on Apple?
yes CoW on APFS has been available for years, but the app has to implement it correctly by using `cp` with the `-c` flag to take advantage of it.
There are also two paid apps on macOS (Hyperspace, https://hypercritical.co/hyperspace/ & diskDedupe, https://diskdedupe.softwar.io/) which will do this, but most users won't know about them, and you would have to scan your whole drive and wait for the app to compare SHA-256 checksums of every file with the same filesize to determine if it's an identical match, then the app has to delete the duplicate files and replace them with the CoW'ed versions of those files. It's not an elegant solution for something that should just be taken care of by the original app (meaning Wine)
Take a look at the Wikipedia article (https://en.wikipedia.org/wiki/Apple_File_System#Clones):
Clones allow the operating system to make efficient file copies on the same volume without occupying additional storage space. Changes to a cloned file are saved as delta extents, reducing storage space required for document revisions and copies.[10] There is, however, no interface to mark two copies of the same file as clones of the other, or for other types of data deduplication.
The feature is automatically available when you copy any files using the Finder application, which is macOS's default file manager, but not when using the cp command.[16] To do that on the command-line, the cp utility on macOS has a -c parameter that allows it to use the clonefile system call.[17]
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #7 from ToastyBug cemer99797@isorax.com --- Someone mentioned on Linux Stack Exchange that in GNU coreutils 9.4, CoW-by-default fails for macOS when overwriting files, and that you should call `/bin/cp -c` instead of plain `cp -c` as a workaround
https://unix.stackexchange.com/questions/311536/cp-reflink-auto-for-macos-x/...
Also see the GNU coreutils documentation:
`--reflink=auto`: If the copy-on-write operation is not supported then fall back to the standard copy behavior. This is the default if no --reflink option is given.
https://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #8 from Brendan Shanks bshanks@codeweavers.com --- (In reply to ToastyBug from comment #5)
(In reply to Brendan Shanks from comment #3)
This would be a good improvement to have, but it's unfortunately not straightforward to implement. Windows also has copy-on-write support in the ReFS file system, and there are APIs to use it (see https://learn.microsoft.com/en-us/windows-server/storage/refs/block-cloning) which can clone a specified number of bytes in one file to another existing file. (Linux also has similar reflink APIs).
macOS only has clonefile(2) which copies an entire file to a new filename, and no "specific bytes" or even "preexisting file" API. This is not a good match with the Windows CopyFile() APIs (and the fairly new NT kernel syscall implementing it), which work by first creating the new file, setting attributes, and then copying the bytes in.
I filed a feedback with Apple 2 years ago asking for a system call that can clone parts of files into existing files, without that I don't think this could ever be implemented in upstream Wine. (It might be possible in a downstream like CrossOver though).
Why should Wine need to clone parts of files into existing files? When you first run Wine (with no WINEPREFIX created), Wine will create an entirely new WINEPREFIX and duplicate (copy) fresh files from Wine.app/Contents/Resources/wine/lib/wine/ to $WINEPREFIX/drive_c/windows/. In this case, APFS copy-on-write is exactly what is needed to make all those files "references" to the originals, without actually copying data blocks on disk.
Copying bytes from one existing file to another is how Wine (and Windows) copies files. Create the destination file, read bytes from the source file, write them to the destination. clonefile(2) copies an entire file to a new name, which may have a similar end result but is not a drop-in way to implement the above.
Populating a new prefix is done by wineboot (with a lot of code also in setupapi), and it's quite complicated. Improving prefix creation (both in speed and disk space) is something I'd like to do, but there's no low-hanging fruit I've found yet.
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #9 from ToastyBug cemer99797@isorax.com --- (In reply to Brendan Shanks from comment #8)
Copying bytes from one existing file to another is how Wine (and Windows) copies files. Create the destination file, read bytes from the source file, write them to the destination. clonefile(2) copies an entire file to a new name, which may have a similar end result but is not a drop-in way to implement the above.
But actually that's exactly what it's supposed to be: a drop-in replacement for the "old" way of copying files. That's why GNU coreutils switched to it by default in coreutils 9.1, so that copy-on-write on macOS APFS volumes would be activated without developers even noticing.
Do you have access to macOS? You can try copying a multi-GB directory or file in the macOS Finder (with the right-click "duplicate" command, or by just copying and pasting). It should be almost instant in the case of 1 large file, and very fast even with many small files. Now try copying using the macOS Terminal with `cp` (slow, non-CoW) and then try again with `cp -c` (fast CoW, same behavior as using Finder).
http://bugs.winehq.org/show_bug.cgi?id=58155
Zeb Figura z.figura12@gmail.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |z.figura12@gmail.com
--- Comment #10 from Zeb Figura z.figura12@gmail.com --- The point is Windows has no equivalent to clonefile(2). We must implement CopyFile() on top of lower-level Windows syscalls, not directly on top of Unix clonefile(2), but there are no lower-level Windows syscalls that can be implemented on top of clonefile(2).
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #11 from Zeb Figura z.figura12@gmail.com --- FWIW, the right solution to this is probably not to use APFS clones at all, but rather to implement Wine reparse points, and invent a Wine-specific reparse point for prefix copies. This would no longer rely on any specific OS. I believe Alexandre specifically prescribed this somewhere but I do not remember where.
http://bugs.winehq.org/show_bug.cgi?id=58155
--- Comment #12 from ToastyBug cemer99797@isorax.com --- Brendan Shanks comment #8:
Populating a new prefix is done by wineboot (with a lot of code also in setupapi), and it's quite complicated. Improving prefix creation (both in speed and disk space) is something I'd like to do, but there's no low-hanging fruit I've found yet.
Instead of using wineboot to populate a new WINEPREFIX, can it be done with standard Unix tools for Linux and macOS? And once the prefix is created, wineboot (or whichever wine exectuable is appropriate) could take over.
Could this be tested by manually copying all the correct files with the macOS Finder from Wine.app/Contents/Resources/wine/lib/wine/ to $WINEPREFIX/drive_c/windows/? Does it work in such a case?
If instead of plain copying, wineboot has to create files that are not binary-identical to those included in Wine.app, could the files that belong in the WINEPREFIX instead be included in the Wine.app binary build, and the APFS cloning method used to populate the WINEPREFIX?
Linux also supports CoW with the XFS and BTRFS filesystems. Does Linux also take up an additional ~300 MB per WINEPREFIX?