ファイルを1件消すだけの処理が、フォルダを丸ごと消す——そんなことある?と思うかもしれませんが、あります。この記事では、その古典的な事故がどう成立して、どうガードすべきかをまとめます。
1. どんな事故か 💥
「権限が切れたデータを1件だけ消す」——リリースしたのはそんな何気ない処理なのに、消えたのはその1件じゃなくて、ユーザーがダウンロードしたデータ全部。削除処理には、そんな事故のパターンが実在します。
削除処理そのものは正しく動いています。バグっているのは処理じゃなくて、処理に渡された「削除対象のパス」。そこに入っていたのは、なんと空文字("")。
たった1つの空文字が、フォルダ丸ごとの削除を引き起こした
なぜそんなことが起きるのか。どうすれば防げるのか。これは特定の言語やアプリに限った話じゃなくて、削除処理を書く全エンジニアに関係する話です。
2. なぜ空文字が「全削除」になるのか 🤔
多くのファイル削除処理は、だいたいこういう形をしています。
削除するパス = 基準フォルダ + ファイル名
たとえば基準フォルダが /データ置き場/、ファイル名が 動画A.mp4 なら、消えるのは /データ置き場/動画A.mp4。ここまでは何の問題もありません。
じゃあ、ファイル名に空文字が入ったらどうなるか。
削除するパス = "/データ置き場/" + "" = "/データ置き場/"
パスが指すのは基準フォルダそのもの。さらに、使っている削除APIがフォルダを中身ごと消せるタイプだと、「このファイル消して」のつもりが「このフォルダ丸ごと消して」に化けるわけです。
解体業者に例えると 🏢
「パスがフォルダに化ける」と言われてもピンとこないかもしれないので、例え話をひとつ。削除処理とは、いわば解体業者に指示書を渡す仕事です。
「△△ビルの 301号室 の内装を撤去してください」
この「301号室」の部分が空欄のまま業者に渡ると、指示書はこう読めてしまいます。
「△△ビルの を撤去してください」
↓
「△△ビルを撤去してください」
業者は指示書どおりに仕事をして、ビルを丸ごと解体。「ビル名 + 部屋番号」の部屋番号が抜けると、指す対象が部屋からビル全体に化ける——「基準フォルダ + ファイル名」で空文字が入ったときに起きるのは、まさにこれです。
怖いのは、業者(削除処理)は指示どおりに動いただけで、エラーが一切出ないこと。処理としては大成功です。ユーザーのデータが全滅した状態で。
3. 事故は「2つの穴」が重なって起きる 🧀
このシナリオを掘り下げると、原因は1つじゃなくて、独立した2つの問題のコンボです。
| 問題 | 内容 | |
|---|---|---|
| 上流 | 対象外のデータが混入した | 削除対象を選ぶ処理で、本来対象外の「実体ファイルを持たないデータ」(フォルダの見出しのようなもの)が絞り込み漏れで混入。ファイルを持たないので、パスが空なのはある意味当然 |
| 下流 | 想定外の値を弾かなかった | 削除処理側は「パスには必ずファイル名が入ってるでしょ」と信じきっていて、空文字の可能性をノーマーク。そのまま結合して基準フォルダを削除 |
大事なのは、どっちか片方だけなら事故にならなかったという点です。
- 上流が正しく絞り込んでいれば → 空パスは流れてこない
- 下流が空文字を弾いていれば → 全削除は起きない
2枚のチーズの穴がたまたま一直線に並んだときだけ、弾が貫通する——安全工学でいう「スイスチーズモデル」そのものです。
しかもこの手の事故、普段はまったく症状が出ません。特定の運用(データの移動や構成変更など)が行われたときだけ発火するので、再現が難しく、原因究明にも時間がかかります。
4. 世界中で繰り返されてきた「古典的な事故」 🌍
「空のパスや変数が上位フォルダに化けて大惨事」というパターン、実は業界の有名な落とし穴で、大手でも繰り返し起きています。
Steam(2015年)
Linux版のスクリプトにこういう処理がありました。
# Scary!
rm -rf "$STEAMROOT/"*
Steamのインストールフォルダを移動した環境などで変数 $STEAMROOT が空になり、rm -rf /* 相当が発動——これが原因として指摘されました。被害を受けるのはそのユーザーが書き込み権限を持つファイルなのでOS自体は無事ですが、ホームディレクトリはもちろん、/media 配下などにマウント中だった外付けドライブまで削除対象になった例が報告されています(3TBのバックアップ用ドライブごと消えた、という報告者の体験談が有名です)。
ちなみにコードの直前に注目してください。開発者自身が書いた # Scary!(怖っ!)というコメントが残っていたことで有名です。怖いと分かっていたのに、コメントで済ませてしまった——ガードはコメントじゃなくコードで書きたいですね。
Bumblebee(2011年)
Linuxドライバのインストールスクリプトで、こう書くべきところを——
rm -rf /usr/lib/nvidia-current/xorg/xorg
スペース1つ余分に入れてしまいました。
rm -rf /usr /lib/nvidia-current/xorg/xorg
/usr と /lib/... の2つを消せという命令に化けて、/usr を丸ごと削除。
つまりこれは「珍しい凡ミス」じゃなく、削除処理という行為そのものに潜む構造的な罠。誰でも踏む可能性があるからこそ、対策が定石として確立されています。
5. どう防ぐか:削除処理の3つの心得 🛡️
① 消す前に、入力を疑う
削除処理の入口で、危険な値は必ず弾きます。
- 空文字・nil(null)を拒否する
- ルートや基準フォルダそのものを指すパスを拒否する
「そんな値、来るはずないでしょ」と思っても弾いてください。この手の事故は毎回、「来るはずのない値」が来た結果として起きています。
② 消す前に、範囲を確かめる
削除対象のパスが「期待するフォルダの配下にあるか」を検証してから実行します。1件のファイルを消すつもりの処理が、フォルダやその外側を指していたら、それはもう異常です。
③ 想定外なら、消さずに知らせる
異常な値を検知したら、削除せずにリターンして、エラー監視ツールに通知します。これで、
- 破壊的な誤削除を防げる(誤って消したものは戻せませんが、消し損ねは後から対処できます)
- 上流に潜む別の問題を観測できる(通知が飛べば「異常な値がまだ流れてきてる」とわかる)
の両方が手に入ります。もちろん「消せない」こと自体も本来の目的が果たせていない異常状態なので放置は禁物ですが、だからこそ通知とセットにして、気づける形でfail-safeに倒すわけです。実際の障害対応でも、修正は最終的にこの形へ落ち着くことが多いです。
まとめ ✍️
合言葉は「疑ってから消す」。
追加・作成系の処理はミスってもやり直せますが、削除だけはやり直しがききません。だから削除処理には、ビビりすぎなくらいのガードを入れる価値があるんです。
上流でどれだけ正しくデータを絞っても、削除処理自体が「最後の砦」として自衛していれば、未知の経路から変な値が流れてきても、全滅だけは防げます。