2012-02-21

小小的 script "lndel": 如何在 Linux 下救回已殺掉、但仍開啟的檔案

這個 shell script 已經用了一陣子了,今天看到朋友也用得理所當然,好像沒事一樣。就想說大概也還有人會想用,因為很短,就放上來,供人取用。

當初寫這個 script,是因為想要保留住瀏覽器所播放過的影音檔,特別是 flash video。瀏覽器播放影音檔時,大多會一邊下載到硬碟,一邊播放。順利的影音檔播放,絕對會需要一個 FIFO 的 buffer,因為這時涉及到兩個非同步的數據存取。而這個 buffer 最直接的形式就是一個磁碟檔案。所以,大部分的瀏覽器所播放的影音檔,多會在硬碟上有一個對應的檔案。

我在 Linux(Debian Etch) 上,一直在使用的 Firefox 或是 Iceweasel,都是把這個檔案放在 /tmp/ 上,例如,/tmp/FlashXXIJOE3B。當瀏覽器確定不再需要它時,例如,該頁面已關閉時,就會把這個檔案 remove 掉。趁著關掉瀏覽器的播放頁面之前,只要把這個檔案改個名字,瀏覽器就去不掉它。也可以趁這個時候把它拷貝起來。所以,我一直都沒有去安裝下載 Flash 檔的軟體或 Plug-in。

然而,一些較新版的瀏覽器,像是 Google Chrome,在建立這個檔案之後,例如,/tmp/FlashXXIJOE3B,就立刻將它 remove 掉。但是因為這個檔案還在使用中,也就是說,它還是一個 opened file,請參見 lsof(1),所以在 UNIX-like 系統上,它其實都還存在,也可以在  Linux 的 proc-filesystem 裡找到,例如,"/proc/19917/fd/4"。

這個 script 就是要把一個檔案已經去掉、但仍開啟的檔案,從 proc-filesystem ,以原來的檔名,連結(symbolic link)到檔案原先所在的位置。使用的方法很簡單,只要把以下的程式碼複製下來,抄到 PATH 裡的任何一個資料夾內,取個名字(我是用 "lndel"),別忘了 "chmod 755 lndel",就可以在需要的時候,隨時把這些檔案「救回來」。只要打 "lndel" 就好了。這個 script 會列出所有它所找到的已 removed 掉的檔案,以及它所建立的連結位置。

它的預設行為是,如果在 /proc/ 之下找到原先檔名開頭為 "Flash" 的已殺掉檔案,就會把它連結到 /tmp/ 下面(symbolic link),因為這就是它被建立時的位置,例如,/tmp/FlashXXIJOE3B。但是所搜尋的檔名,可以用選擇開關 "-k",以 regular expression 的語法指定。基本上,它可以抓到任何已經 removed 掉,但是仍然被某些應用程式開著的檔案,不限檔案的種類。

#!/bin/sh
PROG="`basename $0`"
VERS="v0.2a"
AUTH="Nan-shan Chen"
CDATE="C20110215"
DATE="20110321" # v0.2a 1300644998
OPTS="vqdnk:"

TMP_DATA="/tmp/${PROG}_$$.dat"
KEY="Flash"

F_VERBOSE="false"
F_DEBUG="false"
F_ACTION="true"
F_TEST="false"

usage()
{
  echo "$PROG $VERS $AUTH $CDATE/$DATE"
  echo "Link Deleted Files from Proc Filesystem to /tmp/"
  echo "usage: $PROG [-$OPTS]"
  echo ""
  echo " -k <str>: RegExpr to search in lsof output (\"$KEY\")"
  echo ""
  echo " -v: verbose; output informative verbose messages"
  echo " -q: quiet; be as quiet as possible with the messages"
  echo " -n: no action; show what will be done only if started"
  echo ""
  echo " FILES: lsof(1) /proc/[0-9]*/fd/[0-9]* ln(1)"
  echo "        expr(1) grep(1) awk(1) ls(1)"
  echo ""
}

TIME_T="`date +%s`"
TIME_S="`date "+%G-%m-%dT%H:%M:%S %z %Z"`"
PROGDIR="`dirname $0`"
ARGV="$*"

while getopts $OPTS optchar
do
  case $optchar in

    k)  KEY="$OPTARG";;

    v)  F_VERBOSE="true";;
    q)  F_VERBOSE="false";;
    d)  F_DEBUG="true";;
    n)  F_ACTION="false";;

    -)  shift; break;;
    *)  usage; exit 1;;
  esac
done

shift `expr $OPTIND - 1`

$F_DEBUG && echo >&2 "# $PROG: lsof | grep ${KEY}.*(deleted) > $TMP_DATA"

lsof | grep "${KEY}.*(deleted)" > "$TMP_DATA"

$F_DEBUG && echo >&2 "# $PROG: exec 3< "$TMP_DATA""

exec 3< "$TMP_DATA"

N=0
while read -r LINE 0<&3
do
    $F_DEBUG && echo >&2 "# $PROG: got \"$LINE\""
    PID=`echo "$LINE" | awk '{print $2 }'`
    FD=`echo "$LINE" | awk '{print $4 }' | sed 's/^[^0-9]*\([0-9]*\)[^0-9]*$/\1/'`
    ORG=`echo "$LINE" | awk '{print $9 }'`
    $F_DEBUG && echo >&2 "# $PROG: PID=$PID FD=$FD ORG=$ORG" # 1298099273

    if [ -z "$ORG" ] # 1298099273 temp. sanity check for non-null string
    then
        echo >&2 "*** $PROG: ORG=\"$ORG\"! aborting..."
        exit 255
    fi

    PROC_FILE="/proc/$PID/fd/$FD"

    if [ -r "$PROC_FILE" ]
    then

        if ls -l | grep -q "$PROC_FILE" # possibly already renamed? 1300644998
        then
            $F_VERBOSE && echo "# $PROG: $PROC_FILE already linked"
            EXISTS=`ls -l | grep "$PROC_FILE" | sed -ne "s/^.*[0-9][0-9]:[0-9][0-9] \(.*\) ->.*$/\1/p"`
            $F_VERBOSE && echo -n "# $PROG: " && ls -lL "$EXISTS" # 1300644998
        else
            N=`expr $N + 1`
            $F_VERBOSE && echo "# $PROG: ln -s $PROC_FILE $ORG"
            $F_ACTION && ln -s "$PROC_FILE" "$ORG"
            echo -n "# $PROG: " && ls -lL "$ORG" # 1300644998
        fi
    fi
done

$F_DEBUG && echo >&2 "# $PROG: exec 3<&-"
exec 3<&-

$F_VERBOSE && echo "# $PROG: total number of new links = $N" # 1300644998

$F_DEBUG && echo >&2 "# $PROG: rm -f $TMP_DATA"
rm -f "$TMP_DATA"

exit $?

External Links

[1] Liorithiel's blog, getting flash videos from almost deleted files, 2010-11-08
     http://liori.jogger.pl/2010/11/08/getting-flash-videos-from-almost-deleted-files/

沒有留言:

張貼留言