utmapp / UTM

Virtual machines for iOS and macOS
https://getutm.app
Apache License 2.0
26.7k stars 1.34k forks source link

Allow reclaiming unused storage space from guest hard disk #3460

Closed brunocastello closed 2 years ago

brunocastello commented 2 years ago

I posted the following on this discussion and @conath suggested me to add it as a feature request:

Now UTM just needs (along with an ability to save the VMs to an external storage) a way to shrink machine size after its usage to keep them tidy, just like VMware does present that option in Machine configuration. This would be perfect; then I think that VMware would have (it already does have) a fine competitor here with UTM.

Basically you'd need to script something that would "convert" your current qcow2 image to a new qcow2 image, and this process actually does shrink a bit the machine. Say you used +6GB of your 15GB machine, but decided to delete these files, but how to claim the 6GB back?

I usually proceed to a command line qemu-img argument where I can "convert" the same image to another qcow2 image. This reduces somewhat the image size. And I can reclaim back nearly 6GB. However, this process does take some time (Well, VMware also does, even more on Linux distributions).

VMware provides this as a way to reclaim disk space: https://www.askdavetaylor.com/vmware-fusion-is-eating-up-my-hard-drive-help/

UTM could do a similar thing with qemu-img and qcow2 images. Say you have a machine and specified it to have a max 64GB. And you are currently using 34GB of 64GB. Now, what if you install a program (like an Adobe Creative Cloud suite, for example) and it eats up more 6GB, adding up to 40GB of usage, but you regret it and uninstall the whole suite, but you are left with a 40GB qcow2 image instead of 34GB, and unused 6GB? You might want that space back.

VMware deals with it by adding a "Clean Up Virtual Machine" which reclaims the unused 6GB disk space.

I usually deal with it using a command line with qemu-img: qemu-img convert -O qcow2 win11.qcow2 win11_small.qcow2 then I delete win11.qcow2, and finally I rename the win11_small.qcow2 to win11.qcow2

This process does shrink the image from 40GB to 34GB (or maybe even more, depending of your mileage and disk usage).

Basically you would need to do some kind of script to automatize this process and add a button to the machine configuration to do that, just like how VMware does. The process does take some considerable time to finish (depending of your machine image size), so you might need to add a pop-up screen asking the user to wait until the process is finished (VMware does the same for its Linux machines).

JacksonChen666 commented 2 years ago

a possible method that doesn't take up the space to shrink the image is with (ssd) trimming/discards, provided the drives has the options to shrink the image on discards (by default, it doesn't). this method depends on the OS to trim the free space, triggered manually or automatically. i tested with utm by exporting the qemu command, removing the drive from the vm, copied the drive arguments from the exported command, recreated the image (because utm deletes the image without question, which isn't revert-able), and appended ,discard=unmap,detect-zeroes=unmap (source) to the end of the -drive argument. wrote a file with random bytes, removed it, and triggered a trim on empty space. the empty space (which previously had data) was discarded, and the size of the image has reduced while the vm is still up. the time to reduce the size is considerably significant because it doesn't spend time rewriting the entire used space.

here is just an example i did (video, 411 kB)

(editing existing auto-generated qemu arguments isn't a feature and i'd really like that (still advanced but useful in some cases))

for making it smaller by converting with qemu-img, expand my previous comment below for my concerns of such.

comment i previously wrote with other concerns this is a nice feature to have. i just have a concern: systems don't actually zero out the deleted files, and qemu-img removes at most, the consecutive null bytes that's actually being occupying disk space and within the disk image. let's say, a disk image of 64 gb: 30 gb used, 20 gb free but has written data (i.e. space was used but now deleted files live there), and the rest of the 14gb was never used. the image size could be around 52gb. the user presses the button to free up space, but gets about 2gb back. the user could compare the free space in the vm and the disk image, and then report an issue that not all free space is removed (which for what i know, can be resolved by zeroing out the free space under the vm and then going through qemu-img again). i don't know if qemu drives can do trim (if it can do trim on free space then i probably lost a lot of time to zeroing free space). another concern is disk space. what if the user doesn't have enough disk space? then they can't "delete" the unused free space.
conath commented 2 years ago

That sounds like a much better idea.

brunocastello commented 2 years ago

Yeah, I agree. As long as we can reclaim the space back, I dont mind which method works best for us. When I had VMware, I used to run Sdelete on Windows 10 before shutting down and hitting the button to reclaim the disk space. It did work for me back then.

Using the SDelete utility you can securely delete files and wipe free space in Windows 10 to prevent any sensitive data from falling into the wrong hands. Despite SDelete does not have a graphical user interface, it is still easy for anyone to use.

https://docs.microsoft.com/en-us/sysinternals/downloads/sdelete

osy commented 2 years ago

@JacksonChen666 the source says "First of all, you’ll want to switch the drives from VirtIO to SCSI and use the VirtIO SCSI adapter (which supports TRIM since quite some time, while the blk driver only recently learned about it)." But it was written a year ago. Can you confirm that it works without VirtIO SCSI for you?

JacksonChen666 commented 2 years ago

@osy I am very confident that I did not use any other devices other than the auto-generated arguments (v3.0.1-beta) that I removed and then re-added manually so I can modify the arguments.

But if I'm wrong, I might be not aware of that. So I will provide the arguments I modified and used for the drive:

-device virtio-blk-pci,drive=drive0,bootindex=2 -drive if=none,media=disk,id=drive0,file=/Users/jackson/Library/Containers/com.utmapp.UTM/Data/Documents/Linux.utm/Images/data.qcow2,cache=writethrough,discard=unmap,detect-zeroes=unmap

The above was from the QEMU command export (of the vm I just created like 20 minutes ago, still works).

QEMU command export (not modified form) `qemu-system-aarch64 -L /Applications/UTM.app/Contents/Resources/qemu -S -qmp tcp:127.0.0.1:4444,server,nowait -nodefaults -vga none -spice "unix=on,addr=/Users/jackson/Library/Group Containers/WDNLXAD4W8.com.utmapp.UTM/8F0EEB97-BD46-4943-A000-888C0246E351.spice,disable-ticketing=on,image-compression=off,playback-compression=off,streaming-video=off,gl=off" -device virtio-ramfb -device virtio-rng-pci -cpu host -smp cpus=4,sockets=1,cores=4,threads=1 -machine virt,highmem=off -accel hvf -accel tcg,tb-size=1024 -drive if=pflash,format=raw,unit=0,file=/Applications/UTM.app/Contents/Resources/qemu/edk2-aarch64-code.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=/Users/jackson/Library/Containers/com.utmapp.UTM/Data/Documents/Linux.utm/Images/efi_vars.fd -boot menu=on -m 4096 -device intel-hda -device hda-duplex -name Linux -device qemu-xhci,id=usb-bus -device usb-tablet,bus=usb-bus.0 -device usb-mouse,bus=usb-bus.0 -device usb-kbd,bus=usb-bus.0 -device ich9-usb-ehci1,id=usb-controller-0 -device ich9-usb-uhci1,masterbus=usb-controller-0.0,firstport=0,multifunction=on -device ich9-usb-uhci2,masterbus=usb-controller-0.0,firstport=2,multifunction=on -device ich9-usb-uhci3,masterbus=usb-controller-0.0,firstport=4,multifunction=on -chardev spicevmc,name=usbredir,id=usbredirchardev0 -device usb-redir,chardev=usbredirchardev0,id=usbredirdev0,bus=usb-controller-0.0 -chardev spicevmc,name=usbredir,id=usbredirchardev1 -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=usb-controller-0.0 -chardev spicevmc,name=usbredir,id=usbredirchardev2 -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2,bus=usb-controller-0.0 -device usb-storage,drive=cdrom0,removable=true,bootindex=0,bus=usb-bus.0 -drive if=none,media=cdrom,id=cdrom0 -device virtio-blk-pci,drive=drive0,bootindex=1 -drive if=none,media=disk,id=drive0,file=/Users/jackson/Library/Containers/com.utmapp.UTM/Data/Documents/Linux.utm/Images/data.qcow2,cache=writethrough -device virtio-net-pci,mac=02:C4:99:C3:D7:A7,netdev=net0 -netdev vmnet-macos,mode=shared,id=net0 -device virtio-serial -device virtserialport,chardev=vdagent,name=com.redhat.spice.0 -chardev spicevmc,id=vdagent,debug=0,name=vdagent -uuid 8F0EEB97-BD46-4943-A000-888C0246E351 -rtc base=localtime`
Modified QEMU command export (enables discards) `qemu-system-aarch64 -L /Applications/UTM.app/Contents/Resources/qemu -S -qmp tcp:127.0.0.1:4444,server,nowait -nodefaults -vga none -spice "unix=on,addr=/Users/jackson/Library/Group Containers/WDNLXAD4W8.com.utmapp.UTM/8F0EEB97-BD46-4943-A000-888C0246E351.spice,disable-ticketing=on,image-compression=off,playback-compression=off,streaming-video=off,gl=off" -device virtio-ramfb -device virtio-rng-pci -cpu host -smp cpus=4,sockets=1,cores=4,threads=1 -machine virt,highmem=off -accel hvf -accel tcg,tb-size=1024 -drive if=pflash,format=raw,unit=0,file=/Applications/UTM.app/Contents/Resources/qemu/edk2-aarch64-code.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=/Users/jackson/Library/Containers/com.utmapp.UTM/Data/Documents/Linux.utm/Images/efi_vars.fd -boot menu=on -m 4096 -device intel-hda -device hda-duplex -name Linux -device qemu-xhci,id=usb-bus -device usb-tablet,bus=usb-bus.0 -device usb-mouse,bus=usb-bus.0 -device usb-kbd,bus=usb-bus.0 -device ich9-usb-ehci1,id=usb-controller-0 -device ich9-usb-uhci1,masterbus=usb-controller-0.0,firstport=0,multifunction=on -device ich9-usb-uhci2,masterbus=usb-controller-0.0,firstport=2,multifunction=on -device ich9-usb-uhci3,masterbus=usb-controller-0.0,firstport=4,multifunction=on -chardev spicevmc,name=usbredir,id=usbredirchardev0 -device usb-redir,chardev=usbredirchardev0,id=usbredirdev0,bus=usb-controller-0.0 -chardev spicevmc,name=usbredir,id=usbredirchardev1 -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=usb-controller-0.0 -chardev spicevmc,name=usbredir,id=usbredirchardev2 -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2,bus=usb-controller-0.0 -device usb-storage,drive=cdrom0,removable=true,bootindex=0,bus=usb-bus.0 -drive if=none,media=cdrom,id=cdrom0 -device virtio-net-pci,mac=02:C4:99:C3:D7:A7,netdev=net0 -netdev vmnet-macos,mode=shared,id=net0 -device virtio-serial -device virtserialport,chardev=vdagent,name=com.redhat.spice.0 -chardev spicevmc,id=vdagent,debug=0,name=vdagent -uuid 8F0EEB97-BD46-4943-A000-888C0246E351 -rtc base=localtime -device virtio-blk-pci,drive=drive0,bootindex=2 -drive if=none,media=disk,id=drive0,file=/Users/jackson/Library/Containers/com.utmapp.UTM/Data/Documents/Linux.utm/Images/data.qcow2,cache=writethrough,discard=unmap,detect-zeroes=unmap`

I've also noticed that actually, the options doesn't make it actually un-allocate the space that's used, but make it into sparse space (du returns ~1GB but macOS finder shows ~2GB)

ktprograms commented 2 years ago

@osy I can confirm that using ,discard=unmap,detect-zeroes=unmap works with the virtio-blk-pci device, but the documentation isn't clear for which other drive devices unmap is supported.

adespoton commented 2 years ago

As a data point, I've gone another route, and use qemu-img convert -c -O qcow2 on my images. It doesn't free up used blocks, and the resulting images can't be snapshotted, but it does compress both unused space and duplicate data. Combined with zeroing free space in the guest, it provides the smallest read-write images you can use.

Using -c during initial disk creation by default is not a good idea though, as you immediately lose any potential for machine snapshots. I'd be interested to find out if it's compatible with discard=unmap though, to avoid having to zero-out freespace in the guest.

rotu commented 2 years ago

Edit: for some reason putting the drive last (even without the additional flags) makes the network not work? I'm out of my depth.

Took me a few minutes to figure out how to wire it up in UTM, so maybe screenshots will help the next poor bloke: image image