ダウンロードを検証してからファイルを置き換える

ShellBeginner
オンラインで実践に進む

はじめに

Field Notes は、どの資料が破損または欠落しているかを把握しました。運営チームには、それだけを取得し、独立した基準とバイト列を比較して、検証後に各置き換えを確定する修復処理が必要です。取得元は HTTP エラーだけでなく、HTTP としては完全に成功していても誤った文書を返すことがあります。

この実習は、独自に準備されたパックから始まります。前の実習で学んだ SHA256、行全体の読み取り、コマンド置換、完全なファイル名の知識が必要ですが、前の VM のファイルは不要です。Workspace と Download Desk は同じコンテナを共有します。オリジナルの合成 PDF と curl は準備済みです。所要時間は読解と問題の修正を含む作者の見積もりです。

置き換える前に候補をダウンロードする

このステップでは、実際の配布資料を別の練習ディレクトリに保存し、検証します。

UTF-8 テキストを扱えるよう準備された Workspace から始めます。相対ファイル名が見つからない場合は、この絶対パスを使います。pwd と ls pack により、ディレクトリの誤りと名前の入力ミスを区別できます。

cd /home/labex/project/download-desk

信頼できるマニフェストは /opt/labex/download-pack-state/expected.sha256 にあります。形式は最初の実習と同じで、小文字の 64 桁の 16 進数、2 個のスペース、階層のない完全なファイル名です。独立して提供され、読み取り可能で空ではなく、末尾に改行があります。名前は印字可能な ASCII を使い、スペースを含められます。スラッシュ、バックスラッシュ、CR、LF、タブ、および . と .. という名前は対象外です。親にリンクを含まない実ディレクトリを使います。期待されるメンバーは未存在か通常のローカルファイルでなければなりません。リンクとディレクトリオブジェクトは拒否されます。基準と取得元は編集しません。SHA256 の一致はこの信頼できる基準との一致を示し、発行者の身元を示すものではありません。

cat /opt/labex/download-pack-state/expected.sha256

準備された trial ディレクトリは実験用で、運営チームの pack とは別です。mktemp は末尾が X のテンプレートから一意な空ファイルを作り、パスを出力します。これを candidate に保存すると、この Workspace セッション内で使い続けられます。接続し直すとシェル変数が失われる場合があります。古い値を推測せず、この練習手順を最初からやり直してください。

candidate=$(mktemp trial/.incoming.XXXXXX)

curl は HTTP リクエストを送ります。ここで使うオプションの役割はそれぞれ異なります。

  • --get は GET リクエストを送り、--data-urlencode 'name=Workshop card.txt' はファイル名をクエリパラメーターとして安全にエンコードします。引用符はシェル引数を保護し、URL エンコードはリクエスト内のスペースを処理します。
  • --fail は 503 などの HTTP エラーで 0 以外の終了ステータスにします。HTTP 200 で内容が誤っている場合は検出しません。
  • --silent --show-error は進捗表示を隠し、エラーメッセージを残します。--max-time 8 は転送全体を 8 秒に制限します。
  • --output "$candidate" は一時候補にだけ書き込みます。127.0.0.1:8080 は同じコンテナ内に用意されたローカル取得元で、/good は正しいバイト列を返します。
curl --fail --silent --show-error --get --data-urlencode 'name=Workshop card.txt' --max-time 8 --output "$candidate" http://127.0.0.1:8080/good
printf 'Download exit: %s\n' "$?"

期待する表示は Download exit: 0 です。URL を打ち間違えると、curl は接続エラーや HTTP 拒否を報告する場合があります。上のアドレスを修正し、rm -- "$candidate" で現在の候補だけを削除して、mktemp から繰り返します。-- はオプションの終わりを示し、変数の正確な値が自分の実験ファイルを指します。

配布資料の期待ハッシュを、信頼できるマニフェストの対応する行から取り出します。候補とは独立した次の値です。

expected=a9986fb03d5ca72e42753d0ddd11dce7794b0e5a8b253dd4cd356825ca6d0583

パイプ | は、あるコマンドの標準出力を次のコマンドの標準入力につなぎます。次の printf は候補のチェックサム項目を作ります。sha256sum --check がそれを読み、&& は検証成功時だけ mv を実行します。mv -- は検証済み候補を練習用の最終ファイル名に変更します。この練習ファイルは、実験を繰り返す際に置き換えてかまいません。運営チームの選択的修復ルールは pack に適用されます。

printf '%s  %s\n' "$expected" "$candidate" | sha256sum --check && mv -- "$candidate" "trial/Workshop card.txt"

: OK で終わる行を期待します。FAILED なら候補を移動せず、そのファイルだけを削除して修正したダウンロードを繰り返します。検証済みの配布資料を読めます。

cat "trial/Workshop card.txt"
REPAIR DESK: EAST HALL

未検証のダウンロードを最終ファイル名から隔離できました。次に、この順序をパック全体に適用します。

必要なものだけ修復し、失敗後に再開する

このステップでは、処理に 3 つの引数を与え、役立つ成果と失敗時の保護範囲の両方を確認します。

Workspace の /home/labex/project/download-desk から始めます。スクリプトの呼び出し仕様は bash repair-pack.sh MANIFEST SOURCE_URL PACK_DIRECTORY です。引数とインストール済みコマンドを使う単独の Bash プログラムで、隣接する補助ファイルは不要です。最初に各メンバーの実際のバイト列を分類しなければなりません。一致するメンバーはダウンロードせず、バイト列、モード、所有者、mtime を保持します。対象外の pack/notes.txt も同様に保護します。取得してよいのは欠落または不一致のメンバーだけで、1 回の呼び出しにつき各メンバー最大 1 回です。一致する空ファイルは正しい状態です。完成済みパックへの 2 回目の呼び出しは、ダウンロードリクエストを 0 件にし、パックの全状態を保持しなければなりません。

各候補は対象と同じディレクトリに置き、通常の成功時や報告された失敗時に削除します。確定はファイル単位です。後のメンバーが失敗しても、それ以前の検証済み置き換えは残してかまいません。失敗した候補や誤った候補は、対象を決して置き換えません。これはパック全体のトランザクションでも、同時書き込み、電源断、あらゆるプロセス終了に対する保証でもありません。コースのパスだけを使い、並行変更は避けます。

パーサーとハッシュ抽出には前の実習の構文を使います。次の追加要素が置き換えを保護します。

  • [ -r "$manifest" ] は読み取り可能かを確認し、[ -s "$manifest" ] は空でないことを要求します。! はテストを反転します。|| はテスト間の OR、または失敗したコマンドの後のエラー分岐を表します。ここで && は AND です。波括弧は、新しいサブシェルを作らず関連するテストをまとめます。
  • [ -d "$pack" ] はディレクトリを確認します。[ -L "$target" ] は参照先がないリンクも検出します。-e は対象の存在、-f は通常ファイルを確認します。mv の前にリンクや別の種類のオブジェクトを拒否すると、取得元へのリンクをたどったり、候補を誤ってディレクトリ内へ移動したりすることを防げます。
  • temporary= は空の変数で開始します。trap '...' EXIT はスクリプト終了時のクリーンアップを登録します。単一引用符は展開をクリーンアップ時まで遅らせます。-n は変数が空でないことを意味し、削除するのはスクリプト自身が作ったファイルだけです。rm -f は候補が既に削除されていてもエラーにせず、-- はオプションの終わりを示します。
  • continue は次のマニフェスト行へ進みます。exit 1 は失敗を報告します。>&2 は説明を通常出力ではなく標準エラー出力に送ります。名前変更に成功した後で temporary を空にすれば、クリーンアップは確定済みファイルに触れません。

最初の実習で説明した、そのまま保存するヒアドキュメントで、次の完全なスクリプトを保存します。

cat > repair-pack.sh <<'BASH'
#!/usr/bin/env bash
manifest=$1
base=$2
pack=$3
if [ ! -r "$manifest" ] || [ ! -s "$manifest" ]; then
    printf 'A readable nonempty trusted manifest is required.\n' >&2
    exit 1
fi
if [ ! -d "$pack" ] || [ -L "$pack" ]; then
    printf 'A real pack directory is required.\n' >&2
    exit 1
fi
temporary=
trap 'if [ -n "$temporary" ]; then rm -f -- "$temporary"; fi' EXIT
while IFS= read -r line; do
    expected=${line:0:64}
    name=${line:66}
    target=$pack/$name
    if [ -L "$target" ] || { [ -e "$target" ] && [ ! -f "$target" ]; }; then
        printf 'Expected a regular file or absent member: %s\n' "$name" >&2
        exit 1
    fi
    if [ -f "$target" ]; then
        actual=$(sha256sum -- "$target")
        actual=${actual%% *}
        if [ "$actual" = "$expected" ]; then
            printf 'Keep: %s\n' "$name"
            continue
        fi
    fi
    temporary=$(mktemp "$pack/.repair.XXXXXX") || exit 1
    if ! curl --fail --silent --show-error --get --data-urlencode "name=$name" --max-time 8 --output "$temporary" "$base"; then
        printf 'Download failed: %s\n' "$name" >&2
        exit 1
    fi
    actual=$(sha256sum -- "$temporary")
    actual=${actual%% *}
    if [ "$actual" != "$expected" ]; then
        printf 'Checksum mismatch: %s\n' "$name" >&2
        exit 1
    fi
    mv -- "$temporary" "$target" || exit 1
    temporary=
    printf 'Repaired: %s\n' "$name"
done < "$manifest"
BASH

クリーンアップが受け取るのは自分の正確な一時パスだけで、他人のファイルは削除しません。別の安全な一時名や同等のループも使えます。チェックは動作を評価し、このコマンドの綴りや Keep: という出力文言自体は評価しません。

HTTP が成功しても内容が誤る場合を試す

制御された /html エンドポイントは、HTTP 200 で HTML のエラーページを返します。これは実際の応答であり、curl のステータスを模擬したものではありません。次のコマンドは、既存の破損した配布資料を置き換えずに、失敗することが期待されます。

bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/html pack
printf 'Repair exit: %s\n' "$?"

Repair exit: 1 と Checksum mismatch: Workshop card.txt を期待します。PDF は既に正しかったのでスキップされました。誤った候補は削除され、HTTP の成功だけでは確定されませんでした。ls -a は隠し名も表示するので、.repair.* の候補が残っていないことを確認できます。

ls -a pack

正しい取得元を使います。PDF、空のクレジットファイル、個人メモを保持しながら、スクリプトが配布資料と欠落していた地図を修復します。

bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/good pack
(cd pack && sha256sum --check /opt/labex/download-pack-state/expected.sha256)

すべてのメンバーに OK が表示されるはずです。Download Desk に切り替え、再読み込みして PDF と地図を開きます。地図には東入口が記されています。PDF の横の Download を選び、保存したファイルをブラウザーのリーダーで開きます。どちらもローカルパックを使い、取得元から補完しません。固定 PDF ページにはネイティブのズームを使います。Workspace に戻ります。

再ダウンロードせずに繰り返す

準備された取得元は、資料への実際のリクエストごとに、取得元プロセスの識別情報を含む 1 行を requests.jsonl に記録します。各イベントは観察できる前にファイルへ完全に書き出されます。wc -l < FILE はファイルをコマンドにリダイレクトして行数を数えます。資料を要求する操作ではありません。カウンターを読み、処理を再実行し、もう一度読みます。

wc -l < /opt/labex/download-pack-state/requests.jsonl
bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/good pack
wc -l < /opt/labex/download-pack-state/requests.jsonl

2 つの数は同じでなければなりません。4 メンバーすべてが保持されるはずです。全件を再ダウンロードするとリクエストが無駄になり、最終ハッシュが同じでも、正しいファイルのモードやタイムスタンプを変えるおそれがあります。

完成済みパックの外で転送失敗を練習する

mkdir -p は必要に応じて実験ディレクトリを作ります。ディレクトリをリセットはしません。運営チームの完成済みパックはそのまま残します。

mkdir -p trial/fault-test

制御された /error エンドポイントは HTTP 503 を返します。503 を含む curl エラーメッセージと、0 以外のスクリプトステータスを期待します。

bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/error trial/fault-test
printf 'HTTP-error exit: %s\n' "$?"

期待する表示は HTTP-error exit: 1 です。制御された /partial エンドポイントは、宣言した本文の一部だけを送って接続を閉じます。残りのバイトに関する curl エラー 18 が発生し、今回も候補は確定されないはずです。

bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/partial trial/fault-test
printf 'Partial-transfer exit: %s\n' "$?"

期待する表示は Partial-transfer exit: 1 です。接続失敗、HTTP 拒否、HTTP 200 の誤った内容は異なる観察ですが、いずれも未検証の置き換えを防ぎます。同じ正しいエンドポイントと引数で実験を復旧します。

bash repair-pack.sh /opt/labex/download-pack-state/expected.sha256 http://127.0.0.1:8080/good trial/fault-test

マニフェストやディレクトリのパスを打ち間違えると、スクリプトは必要なオブジェクトを示し、0 以外のステータスで終了します。再試行前に pwd、ls、基準の絶対パスを確認します。保存したスクリプトに構文エラーがあれば、上のヒアドキュメント全体で repair-pack.sh だけを置き換えます。入力ミスを直すために運営チームのパックを削除してはいけません。

最終チェックは、実際のパックと保持対象を読み取ります。また、別の名前や制御された HTTP 失敗を含む隔離パックで、変更せずにスクリプトを実行します。既存の破損対象や、正当な空ファイルが欠落した場合も試します。リクエストを送るのはあなたのスクリプトであり、不足した証拠を採点用のダウンロードが補うわけではありません。繰り返し採点しても、実際の作業を修復したりリセットしたりしません。

まとめ

候補をダウンロードして確定前に SHA256 を検証し、再利用可能な選択的修復スクリプトを作成しました。実際の HTTP エラー、成功していても内容が誤った応答、途中で切れた本文が最終ファイル名に入るのを防ぎました。正しいファイルと個人メモを保持し、完成済みパックでの 2 回目の実行ではダウンロードが不要になりました。