2012/06/26

Recent entries from same category

  1. Windows 10 に AF_UNIX が来たので試してみた。
  2. C言語から分かりやすいAPIで扱える JSON パーサ「cJSON」
  3. C++ で STL フレンドリに扱えるJSONパーサ「json.hpp」
  4. SSE を使わなくても HTML エスケープはある程度速くできる。
  5. glib を使ったマイクロフレームワーク「balde」

何に関連して記事を書こうと思った訳でもないです。たんなる一人コードリーディングです。
GNU CoreUtils に入ってる rm を読みました。
GNU Project Archives
http://ftp.gnu.org/gnu/coreutils/
読んだのは coreutils-8.17.tar.xz に入ってる src/rm.c
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 (0EISDIR, _("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 (00, _("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 /
になったとしても消せない、という事が分かった。

以上、コードリーディング終了。

blog comments powered by Disqus