make install でインストールされる物を見つける方法

野良ビルドを趣味とする自分にとって、make は友達。
さて、その友達 (make) が何をインストールするのか、ふと取得してみたくなった。

一般的に、make install をすると次のような感じになる。

# make install
if test -f /usr/local/bin/vim; then \
    mv -f /usr/local/bin/vim /usr/local/bin/vim.rm; \
    rm -f /usr/local/bin/vim.rm; \
  fi
cp vim /usr/local/bin
strip /usr/local/bin/vim
chmod 755 /usr/local/bin/vim

(中略)

cp  ../runtime/doc/*.pl /usr/local/share/vim/vim74/doc
chmod 755 /usr/local/share/vim/vim74/doc/*.pl

(中略)

/bin/sh ./installman.sh install /usr/local/share/man/fr.UTF-8/man1 "-fr.UTF-8" /usr/local/share/vim /usr/local/share/vim/vim74 /usr/local/share/vim ../runtime/doc 644 vim vimdiff evim
installing /usr/local/share/man/fr.UTF-8/man1/vim.1
installing /usr/local/share/man/fr.UTF-8/man1/vimtutor.1
installing /usr/local/share/man/fr.UTF-8/man1/vimdiff.1
installing /usr/local/share/man/fr.UTF-8/man1/evim.1

(以下略)

単純に cp や install コマンドでやっているだけならまだしも、if文とかで分岐されると、単純な解析では出来なくなる。

パケージ管理

ところで、野良ビルドのお供、パッケージ管理ソフト (?) には paco (現在 porg) という物がある。

paco - a source code pacKAGE oRGANIZER for Unix/Linux

porg - a source code package organizer

このコマンドを経由して make を流すと、インストールされたファイルが全て取得できる。
さて、どんなマジックを使っているのか?

fork/exec

paco 2.0.9 のコードを見ると、次の処理がある。

/// paco/log.cpp
/// Log::getFilesFromCommand

  pid_t pid = fork();

  if (pid == 0) { // child

    string command;
    string libpaco = searchLibpaco();

    for (unsigned i = 0; i < mOpt.args().size(); ++i)
      command += mOpt.args()[i] + " ";

    setEnv("PACO_TMPFILE", mTmpFile);
    setEnv("LD_PRELOAD", libpaco);
    setEnv("PACO_DEBUG", gOut.verbosity() > Out::VERBOSE ? "yes" : "");

/// (中略)

    char* cmd[] = { (char*)"sh", (char*)"-c", const_cast<char*>(command.c_str()), NULL };
    execv("/bin/sh", cmd);

    throw XErrno("execv()");
  }
  else if (pid == -1)
    throw XErrno("fork()");

みんな大好き fork/exec で、別プロセスでコマンド(例えば make)を実行している。
さて、この処理だけでは、別プロセスで make が走って、結果を親プロセスは見ていないようだが・・・

LD_PRELOAD

肝心なのは、次の箇所である。

    setEnv("LD_PRELOAD", libpaco);

これは libc の中の関数をフックする仕組みであり、LD_PRELOADが指定されたプロセスで有効となる。 (詳細はGoogle先生へ)

ここで指定されているライブラリ libpaco は、次のようになっている。

/// lib/paco-log/log.c

static int  (*libc_creat)   (const char*, mode_t);
static int  (*libc_link)    (const char*, const char*);
static int  (*libc_open)    (const char*, int, ...);
static int  (*libc_rename)    (const char*, const char*);
static int  (*libc_symlink)   (const char*, const char*);
static int  (*libc_truncate)  (const char*, off_t);
static FILE*(*libc_fopen)   (const char*, const char*);
static FILE*(*libc_freopen)   (const char*, const char*, FILE*);

/// 中略

  libc_creat = lp_dlsym("creat");
  libc_link = lp_dlsym("link");
  libc_open = lp_dlsym("open");
  libc_rename = lp_dlsym("rename");
  libc_symlink = lp_dlsym("symlink");
  libc_truncate = lp_dlsym("truncate");
  libc_fopen = lp_dlsym("fopen");
  libc_freopen = lp_dlsym("freopen");

/// 中略

int creat(const char* path, mode_t mode)
{
  int ret;

  CHECK_INIT;

  ret = libc_open(path, O_CREAT | O_WRONLY | O_TRUNC, mode);

  if (ret != -1)
    lp_log(path, "creat(\"%s\", 0%o)", path, (int)mode);

  return ret;
}

フックする関数として libc_creat が定義されており、それをローカル関数の create に紐付けている。
そして、実際の create は処理をフックして、ログを取っている。

[訂正]
同名の関数でフックが出来るらしい。
libc_creat はフック前の関数を保存しているようだ。

このように、make 等で実際に呼ばれる処理 (open/rename/symlinkなど) をフックして、それをログファイルに記録することで、何がインストールされているのかを確認しているようである。

なるほど、これなら make install の出力に悩まさずに済む。

本当に欲しかった物

さて、これで make install でインストールされる物が分かった。
これを使えば、野良ビルドでインストールされる無法者たちを、煮るなり焼くなりし放題である。

しかし、よく考えたら、アンインストールすることなんて早々無かった。
うーむ、何か面白いことが出来ないだろうかー??