我正在尝试通过脚本压缩虚拟机映像文件,但我想确保该文件没有被访问。我可以检查 virt-manager 是否正在运行,因为它应该是访问图像的唯一程序,但我不知道是否有更好的方法来做到这一点。我还希望脚本继续尝试,直到文件可供压缩。我也不知道该怎么办。
#Check if virt-manager is running
if pgrep "virt-manager" > /dev/null
then
#re-run script until success
else
gzip -k < /home/brady/.vms/windows10/hdd.img > /media/backup/vms/windows10/hdd.$(date +"%F.%T).img.gz
答案1
该lsof
命令可以告诉您文件是否正在使用。您可以将其放入带有while
a 的循环中sleep
,以使其经常检查。
例如:
在窗口 1 中你可以运行sleep 10000 > /tmp/x
在窗口 2 中运行以下脚本:
#!/bin/bash
FILE=/tmp/x
while [ -n "$(lsof "$FILE")" ]
do
sleep 1
done
echo "File $FILE not in use"
现在,当您按下control-C
中止按钮时,sleep
您将在一秒钟左右看到“文件未使用”响应。
答案2
在安装了 inotify 工具的 Linux 上,您可以执行以下操作:
#! /bin/zsh -
file=${1?}
# if it's a symlink, we want the real file, readlink also tells us
# if the file is accessible
file=$(readlink -e -- "$file") || exit
# start inotifywait as a coproc so we can terminate it after we're
# done:
coproc LC_ALL=C inotifywait -me close --format . -- "$file" 2>&1
# Now wait for the "Watches established." messaged. First, that allows us
# to verify inotifywait started properly, and that also avoids the race
# condition where the last file user is gone after our fuser check but
# before the watch is in place
read <&p && read <&p && [ "$REPLY" = "Watches established." ] || exit
# Now watch CLOSE events until the file has no more user:
while fuser -s "$file" && read <&p; do continue; done
printf '"%s" is no longer used, renaming it to prevent new access\n' "$file"
kill %
ret=0
if mv -- "$file" "$file.moved-away"; then
printf 'and now compressing it\n'
pixz -t < "$file.moved-away" > "$file.xz" || ret=$?
mv -- "$file.moved-away" "$file" || ret=$? # move back
else
ret=$?
fi
exit "$ret"
使用inotifywait
,每次文件的 fd 关闭时我们都会收到通知。这意味着我们不必经常检查文件,并且可以在最后一个用户关闭文件后立即开始压缩。
请注意,从我的测试来看,与我最初担心的相反,这也适用于映射文件,因为在这些情况下,事件CLOSE
不是在最后生成的,close()
而是在最后生成的munmap()
(当文件完全释放时)。
fuser -s
将是一种比lsof
.fuser
也更有可能可用,因为它是标准 UNIX 命令(虽然-s
不是标准选项,但 Linux 上的可用版本确实支持它)。
在压缩之前,我们将文件移走以防止进一步访问。
我们使用pixz
(多线程版本的xz
,尽管最新版本xz
现在也支持多线程),因为它提供了比 gzip 更好的压缩比,更重要的是,因为压缩文件是随机访问的(您可以挂载内容或启动它在使用 nbdkit 的 VM 中,而无需解压缩整个映像)。
请注意,像lsof
,fuser
不会检测用作loop
或mtd
设备后端的文件。对于循环设备,您可以使用losetup -j "$file"
它来检查文件是否正在以这种方式使用。例如,您可以在文件移走后插入此循环:
while [ -n "$(losetup -j "$file.moved-away")" ]; do
sleep 1
done
答案3
lsof
是适合这项工作的工具,但默认情况下它会检查所有 PID,因此速度缓慢且占用大量 CPU 资源。幸运的是,有一些方法可以加快速度。
顺便说一句,virt-manager
这不会是保持虚拟机磁盘映像文件/设备打开的过程。那将是二进制文件之一qemu
,例如qemu-system-x86_64
如果您知道只有特定进程可能会打开您的文件,并且您知道或可以获得它们的 PID,那么您可以将它们作为逗号分隔列表提供给lsof -p
.例如
pids=$(pgrep qemu-system | paste -sd,)
[ -n "$pids" ] && lsof -p "$pids" | grep -i filename
更好的是,您可以使用选项指定进程lsof
名称-c
。-c
不需要进程名称的精确匹配,它需要一个模式(最大长度为 15 个字符)。如果需要,您可以在命令行上使用-c
多次。有关详细信息,请参阅man lsof
lsof 常见问题解答。
如果您使用超过 15 个字符,您将收到如下错误消息:
# lsof -c qemu-system-x86_64
lsof: "-c qemu-system-x86_64" length (18) > what system provides (15)
无论如何,举个例子:
# lsof -c qemu-system | grep -i FreeBSD-10.2-RELEASE-amd64.qcow2
qemu-syst 4770 libvirt-qemu 20u REG 8,3 1837236224 403730954 /var/lib/libvirt/images/FreeBSD-10.2-RELEASE-amd64.qcow2
在一个while
循环中,这将是这样的:
pname='qemu-system'
fname='FreeBSD-10.2-RELEASE-amd64.qcow2'
while lsof -c "$pname" | grep -qi "$fname" ; do
sleep 0.1 # don't need to sleep for as long between checks
# but if you're not impatient, leave it at 1 second
# rather than 0.1.
done
echo "$fname is not in use"
如果您为虚拟机使用原始分区(例如fname='/dev/sda5'
在上面的脚本片段中使用),这也适用。
如果您使用 ZFS ZVOL 或 LVM LV 或类似的而不是基于文件的映像,则情况会变得稍微复杂一些。 lsof
在解析任何符号链接后显示实际的块设备名称,因此您还必须解析符号链接(例如使用readlink -f
)和 grep 。
freedos
例如,对于 ZFS,在池上调用 ZVOL volumes
:
# fname=$(readlink --n f /dev/zvol/volumes/freedos)
# echo "$fname"
/dev/zd32
对于具有名为 LV 的 LVM centos7
:
# fname=$(readlink -n -f /dev/mapper/centos7)
# echo $fname
/dev/dm-1
注意:/dev/vg/centos7
代替/dev/mapper/centos7
也有效。
我最初开始编写基于此find
的方法作为答案,但意识到该lsof -c
方法更好。我将其保留下来只是为了记录另一个相当快的替代方案。
find -lname
lsof
与没有PID 选项的情况下运行相比,系统上的速度更快、更轻-p
。
例如
# sleep 10000 > /tmp/foo &
[1] 31077
# find /proc/[0-9]*/fd/ -lname '/tmp/foo'
/proc/31077/fd/1