シェルスクリプト:「ファイルが使用されていない場合」の条件

シェルスクリプト:「ファイルが使用されていない場合」の条件

スクリプトを使用して仮想マシンイメージファイルを圧縮しようとしていますが、ファイルにアクセスしていないことを確認したいと思います。 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ループに入れることができます。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約1秒間「ファイルが使用されていません」という応答が表示されます。

答え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よりも良いでしょうlsoffuser標準UNIXコマンド-s標準オプションではありませんが、Linuxで利用可能なバージョンはそれをサポートしています)。

圧縮する前に追加のアクセスを防ぐためにファイルを移動します。

私たちはgzipよりも優れた圧縮率を提供し、より重要なのは、圧縮されたファイルがランダムにアクセスされるため(最新バージョンもマルチスレッドをサポートしますが、マルチスレッドバージョンを使用しpixzます。コンテンツをマウントまたは開始できます。を解放せずにnbdkitを使用するVMで)。xzxz

または、デバイスのバックエンドとして使用されるlsofファイルは検出されfuserません。ループデバイスの場合、これを使用してファイルがこの方法で使用されていることを確認できます。たとえば、ファイルが移動された後に次のループを挿入できます。loopmtdlosetup -j "$file"

while [ -n "$(losetup -j "$file.moved-away")" ]; do
  sleep 1
done

答え3

lsof作業に適したツールですが、基本的にすべてのPIDをチェックするので、遅くてCPUを大量に使用します。幸いなことに、作業をスピードアップする方法があります。

ただし、virt-managerこれは仮想マシンのディスクイメージファイル/デバイスを開いたままにするプロセスではありません。これはバイナリの1つですqemuqemu-system-x86_64

 

特定のプロセスのみがファイルを開く可能性があり、そのプロセスのPIDを知っているか取得できる場合は、そのプロセスをカンマ区切りリストとして提供できますlsof -p

pids=$(pgrep qemu-system | paste -sd,)
[ -n "$pids" ] && lsof -p "$pids" | grep -i filename

より良いことは、optionsを使用してプロセスlsof名を指定できることです-c-cプロセス名が正確に一致する必要はなく、パターン(最大長15文字)が必要です。必要に応じて、コマンドラインでこれを複数回使用できます-cman lsof詳しくはlsof FAQをご覧ください。

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 -fgrepを使用)。

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 -lnamelsofPIDオプションなしで実行するよりも、システムでより高速で軽量です-p

例えば

# sleep 10000 > /tmp/foo &
[1] 31077
# find /proc/[0-9]*/fd/ -lname '/tmp/foo'
/proc/31077/fd/1

関連情報