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