[Solved] How to Pass Through LVM NTFS Partition to Windows KVM

I have a sintered 8TB HDD that I have short-stroked and am intending to set up an LVM cache on.

I have two LVs (each with an associated PV for short-stroking reasons); the first 4TB half as NTFS for Windows games, and the second 4TB half as bulk storage as ext4. I have set up the ext4 as a filesystem share and that works just fine in Windows 11. However passing through the 4TB NTFS partition doesn’t quite seem to be working.

I end up with a 2048 “GB” (GiB) partition, followed by a 1678.01 “GB” partition, both Unallocated, in Windows, despite the partition already being formatted to NTFS.

Mounting as a VirtIO disk from /dev/mapper/vg-lv or /dev/vg/lv both end up with this weird separation of unallocated pre-formatted NTFS “partition”, and mounting it as a SCSI disk using a VirtIO SCSI controller didn’t make a difference, following the guide linked below.

I’ve read that there’s presumably some sort of issue with passing through a block device that Windows doesn’t see the initial partition table, as I understand it, as alluded to here:

If I understand what’s going on correctly, I guess it doesn’t matter what the NTFS partition actually is (so it could just be an empty ext4 partition, and maybe Windows would see that as a single partition), and Windows will partition it further when creating the partition table.

Unless I am missing something obvious and there’s a better way to do this?

Formatting the LVM to ext4 just wastes overhead, and as this partition is exclusively going to contain games (and some games don’t like being on anything other than NTFS - see Ubisoft games not liking Windows Storage Spaces’ filesystem, which I think is ReFS)

Formatting the LVM to unformatted allows Windows to format it, but Linux doesn’t see it.

Windows is expecting an entire disk, with partition table, as you state. Linux can deal with a block device without a partition table.

Theoretically a hypervisor could synthesize a fake partition table to do that for you, but I don’t know of any with that feature.

Couple of ways:

  1. Create a partition table inside the LV, then pass through the LV to the Windows VM. Windows will see it normally, but the host will need to use the partition table inside the LV. Works fine with a tool like kpartx to handle that for you.

Downside: The LV you already have doesn’t have free space at the start for the partition table, so you’ll need to backup the data, create a partition table inside the LV, re-create the filesystem and restore all the data.

This will probably be the easiest way if you don’t mind restoring all the data, or don’t have any data to begin with.

edit: I initially interpreted your question as having two partitions, but when you say LVM partitions I guess you mean LVs. I’ve edited the above to correct.

  1. Use device-mapper to create a “fake” disk with a partition table from the data partition.

Pro: Don’t need to recreate your NTFS filesystem, use it as-is.

Con: Lots of commands :slight_smile:

Here’s the commands!

This is the partition you want to pass through to the VM:

ntfs_partition=/dev/sdx1

Calculate the size of the partition and the GPT

part_size=$(blockdev --getsize64 ${ntfs_partition})
part_size_sectors=$[part_size / 512]

gpt_size=$[1024*1024]
gpt_size_sectors=$[gpt_size / 512]

Create files for the GPT and a temporary data partition.

It needs to be GPT because you have >=2TB of data. With <2TB of data you can use a single sector for a single MBR partition.

We create a temporary data partition so that we’re not writing to the real partition when we create the partition table.

The files will be created sparse, so a 4TB file doesn’t use any space unless you actually write to it.

truncate --size=${gpt_size} ntfs.gpt.primary.img
truncate --size=${part_size} temp.img
truncate --size=${gpt_size} ntfs.gpt.secondary.img

Setup loopback devices for these (device-mapper needs block devices)

losetup /dev/loop0 ntfs.gpt.primary.img
losetup /dev/loop1 temp.img
losetup /dev/loop2 ntfs.gpt.secondary.img

Create a device-mapper table to describe the device.
This uses the linear target to concatenate the three block devices.

read -r -d '' table <<EOF
0 ${gpt_size_sectors} linear /dev/loop0 0
${gpt_size_sectors} ${part_size_sectors} linear /dev/loop1 0
$[gpt_size_sectors + part_size_sectors] ${gpt_size_sectors} linear /dev/loop2 0
EOF

Create the device-mapper device

echo "$table" | dmsetup create ntfs.with-gpt

Create the partition table

sgdisk \
--new=1:${gpt_size_sectors}:+${part_size_sectors} \
--typecode=1:0700 \
/dev/mapper/ntfs.with-gpt

Check the partition tables looks good

sgdisk -p /dev/mapper/ntfs.with-gpt
Disk /dev/mapper/ntfs.with-gpt: 8589938688 sectors, 4.0 TiB
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): C4741A85-8972-451E-871A-BA2181D78ADF
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 8589938654
Partitions will be aligned on 2048-sector boundaries
Total free space is 4029 sectors (2.0 MiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048      8589936639   4.0 TiB     0700  

We’ve finished writing.
Remove the temporary NTFS partition used to create the partition table.

dmsetup remove /dev/mapper/ntfs.with-gpt
losetup -d /dev/loop1
losetup /dev/loop1 ntfs.img

Create a new device mapper table using the real NTFS partition

read -r -d '' table <<EOF
0 ${gpt_size_sectors} linear /dev/loop0 0
${gpt_size_sectors} ${part_size_sectors} linear ${ntfs_partition} 0
$[gpt_size_sectors + part_size_sectors] ${gpt_size_sectors} linear /dev/loop2 0
EOF
echo "$table" | dmsetup create ntfs.with-gpt

Now you have a /dev/mapper/ntfs.with-gpt block device which you can pass to a VM which has the correct partition table.

1 Like

I don’t yet have any data on it (it’s taking up space on other drives right now, which is annoying, but I’m also yet to set up the LVM cache as I also want to benchmark the difference in performance. Whether I’ll only see it in real-world scenarios is possible, but I mainly just want to do it :b ).

Yes, I did mean LVs (with an associated PV each so that it’s actually short-stroked), rather than LVM partitions. I have edited my post to be clearer.

Out of interest, would either or both of those two options end up with an NTFS partition that’s mountable in Linux? Not that I need to, but it would be nice to be able to access the data outside of Windows. (As currently, with an unformatted partition passed through to Windows and then partitioned in Windows, Linux doesn’t know what to make of it.)

Ok, that gives you some freedom, I’d go for the first method personally as there is less setup and less to go wrong.

Both methods would allow the Linux host to access the data in the partition. It’d work like this:

Assuming we already have an LVM PV and volume group setup, let’s create an LV for the disk with the NTFS filesystem we’ll pass through to a Windows VM:

lvcreate -n lv_ntfs_disk --size 4T /dev/vg0

Create a GPT partition table in the LV device:

gdisk /dev/vg0/lv_ntfs_disk
n (new)
<enter> (default first sector of +1M)
<enter> (default end of full device)
0700 (Windows basic data partition typecode)
w (write)

For partitioning normal block devices, you’d get extra block devices created here automatically for each partition, but LVM doesn’t automatically do that (it can, with some lvm.conf and udev tweaks, but I don’t know the syntax from the top of my head).

So we can use kpartx to create device-mapper devices representing each partition:

kpartx -a /dev/vg0/lv_ntfs_disk

That makes a /dev/disk/by-id/dm-name-vg0-lv_ntfs_disk1 device representing partition 1 in that LV.

Now you can mkntfs -Q and ntfs-3g to mount that:

mkntfs -Q /dev/disk/by-id/dm-name-vg0-lv_ntfs_disk1
Cluster size has been automatically set to 4096 bytes.
Creating NTFS volume structures.
mkntfs completed successfully. Have a nice day.

ntfs-3g /dev/disk/by-id/dm-name-vg0-lv_ntfs_disk1 /mnt/ntfs

Now /mnt/ntfs is the mounted NTFS filesystem from GPT partition 1 in the LV.

Remember to unmount it before you pass-through the LV to the VM!

1 Like

Wow, just scanned over the replies. Try to keep it simple.

If the 4TB NTFS drive is still empty, do the following:

Remove the NTFS partition. You need a blank, unformatted LV !

In virt-manager, define a new storage device:

Choose “Select or create custom storage” and click “Manage…”.

Depending on what you see in the new window, you need to select the option where your LV is located, or click “+” to create a new storage pool.

The resulting configuration (open the xml tab) will look something like that:

<disk type="block" device="disk">
  <driver name="qemu" type="raw" cache="none" io="native" discard="unmap" iothread="1"/>
  <source dev="/dev/vg/ntfs_lv"/>
  <target dev="vdd" bus="virtio"/>
  <address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
</disk>

Make sure it says type=“raw”

Once everything is configured in virt-manager, start the Windows VM.

In Windows you need to open storage manager or whatever it’s called and it will show you an empty partition.

Format it to NTFS under Windows!

And voila - you got an NTFS partition on LVM. On you Linux host you can deal with that partition like any partition. Just mount it like:

mount -t ntfs-3g -o "$rw_mode",nls=utf8,umask=000,dmask=027,fmask=137,uid=$(id -u $user),gid=$(id -g $user),windows_names "$mount_dev" "$mount_path"

I wrote a script to backup such an NTFS LV, see “Remote Backup Script for Windows NTFS Partitions on LVM Volumes”.

I also recommend using the ntfs-3g driver under Linux, not the new ntfs3 kernel driver, see “Does the Linux NTFS3 Driver Corrupt Directories?

Good luck!

Yep, I probably should’ve replied to that last post, but I’ve got it all working. Thanks, you both : )

The unformatted LV was what I should’ve been doing (and what I’ve done), and passing that through to Windows allowed Windows to “initialise” the LV by adding a partition table etc, and once the NTFS partition was added in Windows, I can mount it in Linux via kpartx - I managed to skip a lot of commands that way!

The only thing I want to do now is add it to my KDE widgets to monitor storage use, but even mounting the NTFS partitions as read-only doesn’t update when the files are changed in the VM… Is there a way around this, or should I just not be thinking about doing this anyway?

@powerhouse, presumably I wouldn’t want discard="unmap" as it’s a HDD not an SSD, even if it is accelerated with LVM-cache?

Speaking of it being a HDD, and being NTFS, do I need to worry about defragging it? Or would that just not work anyway?

Just revisited the thread. Yep, unmap is no good for HDD. And I did forget to mention the kpartx command when mounting an NTFS partition.
Talking about mounting an NTFS partition residing in a LV, I wrote a script to detect the right sub-partition in Windows and mount it on the Linux host: https://www.heiko-sieger.info/mounting-an-lvm-based-ntfs-partition-on-the-linux-host/. Hope it’s helpful.

Defragging: If it’s an HDD, then yes, eventually the NTFS partition will get fragmented. But I wouldn’t worry about it, unless you have lots of file creation and deletion going on on that drive. I never defragged a NTFS partition in recent years, since I use NVMe or SSD drives for the OS and as work space, and the large HDD only for storage and backup. Every couple of years, when the HDD drives get old and the capacity of new HDDs rises significantly at same or lower cost, I copy the data onto new drives. By copying files to another HDD the entire data will be defragged.