Mounting an LVM-based NTFS Partition on the Linux Host

I use LVM or “Logical Volume Manager” for all my storage needs. LVM provides a layer of abstraction between physical storage devices (such as hard drives or SSDs) and the file systems that are created on them. Among the file systems are also NTFS partitions that I created under Microsoft Windows 10. Here I share a little BASH script that mounts an LVM-based NTFS partition on the Linux host.

LVM or Not?

Mounting an NTFS file system on Linux is as easy as typing:

mount -t ntfs-3g /dev/device_to_mount /mnt/mount_point

That is true for “normal” NTFS partitions created directly on the media (hard drive, SSD, etc.). But NOT for LVM-based partitions created within a VM (virtual machine).

For the Linux host to be able to mount a NTFS partition created by Windows on an LVM-formatted drive, you need to use a utility that reads the partition table on the LVM volume and creates device maps for those partitions. I’m using the kpartx utility which is probably the easiest way to mount those partitions.

The thus created device maps appear under /dev/mapper, for example:

[user@pc ~]$ ls /dev/mapper
control              vmvg-ubuntu
host-data            vmvg-ubuntu-real
host-home            vmvg-ubuntusnap
host-root            vmvg-ubuntusnap-cow
host-swap            vmvg-win10
media-movies         vmvg-win10p1
media-photo_raw      vmvg-win10p2
photos-photo_stripe  vmvg-win10p3
photos-tmp_stripe    vmvg-win10p4
vmvg-centos8         vmvg-workdrive

Note the vmvg-win10 entry and its siblings vmvg-win10p1 to vmvg-win10p4. The latter four entries were created by kpartx as it discovered that the logical volume (LV) “vmvg-win10” actually contains 4 partitions.

Script for Mounting NTFS Partitions on LVM

I wrote the following BASH script to mount and unmount NTFS partitions that were created by Microsoft Windows on LVM raw volumes. It requires root privileges to execute the kpartx and mount commands.

For the script to work, you must have the following packages installed:

  • ‘kpartx’ from package ‘multipath-tools’
  • ‘virsh’ from package ‘libvirt’
  • ‘notify-send’ from package ‘libnotify’

I’ve chosen the user-space ntfs-3g driver to mount the NTFS partition(s), because I discovered file corruption with the new ntfs3 kernel driver. This ntfs3 kernel driver issue may be fixed by now, but I don’t know. You can change the driver inside the script by un-commenting the mount -t ntfs3 option and commenting out the mount -t ntfs-3g option.

Usage

Run the script with “sudo” privileges as follows:

sudo ./vm-mount.sh vmvg-win10

where “vmvg-win10” is the name of the LVM volume containing the NTFS partition(s), specified as volume_group<hyphen>logical_volume. See ls /dev/mapper listing above for reference.

When mounting a NTFS partition inside a LVM volume, the script will automatically mount the largest partition in that volume if it finds multiple partitions – see above vmvg-win10p1 to vmvg-win10p4. (Microsoft Windows is a champion of obfuscation – every once in a while it creates yet another hidden data recovery partition without asking the user.)

The script checks to see which virtual machine is associated with the LVM volume and determines if the VM is currently running. If yes, it mounts the partition in “read-only” mode. If the VM is shut-down, it mounts the NTFS partition in “read-write” mode. The mount point is “/mnt/vm_volume”, for example /mnt/vmvg-win10/.

Once the NTFS volume is mounted, it will open the mounted NTFS partition in a new file browser window.

Warning: Never start the associated virtual machine while the partition is mounted in read-write mode! This can lead to data loss or even the erasure of the entire partition. This is because Microsoft Windows cannot know about the Linux host writing to the partition. Use a network (smb) share on Windows to allow the Linux host to write to a Windows partition while the VM is running.

The script is like a toggle. If the NTFS partition hasn’t been mounted, it will mount it. If it’s already mounted, it will unmount it.

When unmounting a partition, if the drive is busy and cannot be unmounted, the script will run a loop and asks you to finish what you are doing. Close the document(s) you may have opened or the terminal screen that accesses the NTFS partition, or wait for file copy actions to complete.

Using a Launcher

The easiest way to use the script is the following: Create a launcher on your desktop with the path to the script and the name of the LVM volume (e.g. vmvg-win10). When you click the launcher the first time it will mount the NTFS partition. The next time you invoke the launcher it will unmount the NTFS partition, and so on. Here an example for a desktop launcher entry:

gksu /home/user/.local/bin/vm-mount.sh vmvg-win10

Note the gksu – it requires you to enter your password every time you invoke the script. I don’t want to accidentally mount a Windows VM partition on Linux, so this is a safeguard.

Error Codes

The script will print message boxes using “notify-send” to keep you informed and to alert in case of an error. In case of an irrecoverable error, it will exit with an exit code. Here the list of exit codes:

  • exit 1 – unknown error
  • exit 2 – dependencies not met / missing package
  • exit 3 – script called without ‘sudo’ root privileges
  • exit 4 – usage error – missing vm_volume parameter
  • exit 5 – vm_volume not found
  • exit 6 – vm_volume not associated with a VM
  • exit 7 – vm_volume is not an NTFS file system
  • exit 8 – could not mount vm_volume

The script can easily be modified and expanded to mount different LVM-based file systems supported by Linux, such as ext4, btrfs, fat32/exfat, and more.

As always: USE AT YOUR OWN RISK!

BASH Script

Download the BASH script by clicking the DOWNLOAD button below.


Go to your download folder and change the file extension to .sh:

mv vm-mount.txt vm-mount.sh

Make the file executable:

sudo chmod +x vm-mount.sh

Place the script in a suitable folder, for example ~/.local/bin:

mv vm-mount.sh ~/.local/bin

Then make sure nobody but the root user can execute or modify the file:

sudo chown root:root ~/.local/bin/vm-mount.sh
sudo chmod 700 ~/.local/bin/vm-mount.sh

The above steps are important to prevent unauthorized use or modification of the script.

Here the script for reference:

#!/bin/sh
#
# vm-mount.sh - mount Windows NTFS partitions residing on LVM volumes.
#
# Copyright (C) 2024 Heiko Sieger
# https://heiko-sieger.info
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
# Usage: sudo vm-mount.sh vm_volume
#
# where "vm_volume" is the name of the volume as found in
# "/dev/mapper/", for example "volume_group-windows_lv"
#
# Dependencies
# ============
#
# The script requires the following:
#	'kpartx' from package 'multipath-tools'
#	'virsh' from package 'libvirt'
#	'notify-send' from package 'libnotify'
#
vm_volume="$1"
user=$(logname)
rc=0
export $(dbus-launch)
mount_vol ()
{
	mkdir -p "$mount_path"
	rw_mode="$1"
	case $rw_mode in
		rw)
			notify-send "$vm_name is shut off" "Mounting volume $vm_volume in read-write mode." -i dialog-information
			;;
		ro)
			notify-send "$vm_name is running" "Mounting volume $vm_volume in read-only mode." -i dialog-information
			;;
		*)
			exit 1
			;;
	esac
#	mount -t ntfs3 -o "$rw_mode",iocharset=utf8,dmask=027,fmask=137,uid=$(id -u $user),gid=$(id -g $user),discard "$mount_dev" "$mount_path"
	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"
	rc=$?
}
umount_vol ()
{
	# Loop until vm_mount can be unmounted
	while fuser -sMm "$mount_path"; do
		notify-send -wt 10000 "Cannot unmount $vm_volume - device is busy" "Close all files in $vm_volume and click message to try again." -i dialog-warning
	done
	
	umount "$mount_path" && notify-send "Info" "Unmounted volume $vm_volume." -i dialog-information
	sleep 2
	rmdir "$mount_path" || notify-send -t 0 "Error" "Could not remove directory $mount_path" -i dialog-error
	kpartx -d "$vm_path" || notify-send -t 0 "Error" "Could not unmap $vm_volume" -i dialog-error
}
# See that the required packages are installed
if ! type notify-send 2>2; then echo "Error: notify-send from package libnotify is not installed. Abort."; exit 2; fi
if ! type kpartx 2>2; then notify-send -t 0 "Error" "kpartx from package <b>multipath-tools</b> is not installed. Abort." -i dialog-error; exit 2; fi
if ! type virsh 2>2; then notify-send -t 0 "Error" "virsh from package <b>libvirt</b> is not installed. Abort." -i dialog-error; exit 2; fi
# Run as root
if [ $UID -ne 0 ]; then
	notify-send -t 0 "Error" "Script must be run as '<b>root</b>' (sudo). Abort." -i dialog-error
	exit 3
fi
if [ -z "$vm_volume" ]; then
	notify-send -t 0 "Usage error" "Usage: <i>sudo ./vm-mount.sh volume_group-logical_volume</i>" -i dialog-error
	exit 4
fi
vm_path="/dev/mapper/${vm_volume}"
mount_path="/mnt/$vm_volume"
# Check input. If vm_path doesn't exist, exit
if [ ! -b "$vm_path" ]; then
	notify-send -t 0 "Error" "Volume $vm_volume does not exist. Abort." -i dialog-error
	exit 5
fi
# Determine vm_name associated with vm_volume
vm_dev="$(echo $vm_volume | sed 's|-|/|')"
found="no"
domains="$(virsh list --all --name)"
for vm_name in $domains; do
	if [[ "$(virsh -q domblklist $vm_name | awk '{ print $NF }' | grep -x /dev/$vm_dev)" ]]; then
		found="yes"; break
	fi
done
if [[ $found != "yes" ]]; then
	notify-send -t 0 "Error" "$vm_volume not associated with any VM. Abort." -i dialog-error
	exit 6
fi
# See if the volume is already mounted and if it is of type "fuseblk" or ntfs3.
if cat /proc/mounts | grep -qE "${mount_path} (fuseblk|ntfs3)"; then
	umount_vol
else
	# Create a mapper device for each partition in the vm_volume and
	# assign the largest partition to $dev
	kpartx -av "$vm_path"
	if [ -b "${vm_path}p1" ]; then
		dev="$(lsblk -no NAME,SIZE ${vm_path}p? | sort -h -k 2 | awk '{ print $(NF-1) }')"
		dev="$(echo $dev | awk '{ print $NF }')"
	else
		if [ -b "${vm_path}1" ]; then
			dev="$(lsblk -no NAME,SIZE ${vm_path}? | sort -h -k 2 | awk '{ print $(NF-1) }')"
			dev="$(echo $dev | awk '{ print $NF }')"
		else
			dev="$vm_volume"
		fi
	fi
	mount_dev="/dev/mapper/$dev"
	# Make sure mount_dev is of type ntfs
	if [[ "$(lsblk -no FSTYPE ${mount_dev})" != "ntfs" ]]; then 
		notify-send -t 0 "Mount error" "$vm_volume is not an NTFS file system! Abort." -i dialog-error
		kpartx -d "$vm_path"
		exit 7
	fi
	
	# Mount rw if VM is shutoff, else mount ro
	if virsh list --name --state-shutoff | grep -xq $vm_name; then
		mount_vol rw
	else
		mount_vol ro
	fi
	
	if [ $rc = 0 ] ; then
		# Open file browser as user 1000
		# sudo -u \#1000 -H nohup xdg-open "$mount_path" &
		sudo -u "$user" -H nohup xdg-open "$mount_path" &
		exit 0
	else
		notify-send -t 0 "Mount error" "Could not mount volume $vm_volume !" -i dialog-error
		# clean up
		kpartx -d "$vm_path"
		rmdir "$mount_path"
		exit 8
	fi
fi
#EOF
# exit codes:
# exit 1 - unknown error
# exit 2 - dependencies not met / missing package
# exit 3 - script called without 'sudo' root privileges
# exit 4 - usage error - missing vm_volume parameter
# exit 5 - vm_volume not found
# exit 6 - vm_volume not associated with a VM
# exit 7 - vm_volume is not an NTFS file system
# exit 8 - could not mount vm_volume

Author: Heiko Sieger

The day has 24 hours. If that isn't enough, I also use the night.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.