GNU CoreUtils に入ってる rm を読みました。
GNU Project Archives読んだのは coreutils-8.17.tar.xz に入ってる src/rm.c
http://ftp.gnu.org/gnu/coreutils/
preserve_root 変数は 203行目にある main で
int
main (int argc, char **argv)
{
bool preserve_root = true;
struct rm_options x;
true に初期化されていて319行目
if (x.recursive && preserve_root)
{
static struct dev_ino dev_ino_buf;
x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
if (x.root_dev_ino == NULL)
error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
quote ("/"));
}
で参照される。x.recursive は -r もしくは -R が指定された場合に true となる。つまり「--no-preserve-root」が指定されていない場合に入る。get_root_dev_ino は lib/root-dev-ino.c にある。
if (lstat ("/", &statbuf))
return NULL;
ド頭で / を確認しているのでもしlstatに失敗したら上記の319行目のコードは
if (x.root_dev_ino == NULL)
error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
quote ("/"));
に遷移する。error 関数は lib/error.c にあり、そこから error_tail へ。
if (status)
exit (status);
error 関数は EXIT_FAILURE (値1)で呼び出しているのでこの後はプログラムは終了。つまり --no-preserve-root を付けた場合で / のデバイス情報が取れる場合だけ削除処理に入る。逆に上記の x.recursive が false つまり、-r や -R を指定しなかった場合、src/remove.c にある rm 関数に入る。rm 関数では引数 file は / として渡り、xfts_open 関数(fts_open の wrapper)へ渡り、ディレクトリリストの一部として rm_fts に渡る。/ はディレクトリなので rm_fts でいきなり入る switch 文
switch (ent->fts_info)
{
case FTS_D: /* preorder directory */
if (! x->recursive)
{
/* This is the first (pre-order) encounter with a directory.
Not recursive, so arrange to skip contents. */
error (0, EISDIR, _("cannot remove %s"), quote (ent->fts_path));
mark_ancestor_dirs (ent);
fts_skip_tree (fts, ent);
return RM_ERROR;
}
の FTS_D のケースに入る。ここには x.recursive は false で入ったはずなので、ブロック内のエラーメッセージが表示され、配下のファイルもスキップされる。関数の戻り値は RM_ERROR。rm 関数でエラーコード rm_status が s で更新される。ディレクトリリストとしては / だけなのでそのまま終了。戻り値は RM_ERROR。main に戻ってきて
exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS);
EXIT_FAILURE で exit してプログラム終了。-r --no-root-preserve の場合は
if (ent->fts_level == FTS_ROOTLEVEL)
{
if (strip_trailing_slashes (ent->fts_path))
ent->fts_pathlen = strlen (ent->fts_path);
/* If the basename of a command line argument is "." or "..",
diagnose it and do nothing more with that argument. */
if (dot_or_dotdot (last_component (ent->fts_accpath)))
{
error (0, 0, _("cannot remove directory: %s"),
quote (ent->fts_path));
fts_skip_tree (fts, ent);
return RM_ERROR;
}
/* If a command line argument resolves to "/" (and --preserve-root
is in effect -- default) diagnose and skip it. */
if (ROOT_DEV_INO_CHECK (x->root_dev_ino, ent->fts_statp))
{
ROOT_DEV_INO_WARN (ent->fts_path);
fts_skip_tree (fts, ent);
return RM_ERROR;
}
}
上記で得た x.root_dev_ino を使いガードされている。
まとめると
GNU CoreUtils に入っている rm を使っている限り、--no-preserve-root を指定しないと / は消せない
事になる。冗長的に言うと
# rm -r /$FOO
は、対話型でないシェルスクリプトから起動したとしても、-f を付けたとしても、ユーザが root であったとしても関係無く、--no-preserve-root を付けないと消せない という事になり、さらに $FOO が宣言されていなくて
# rm -r /
になったとしても消せない、という事が分かった。以上、コードリーディング終了。