CentOS で/lib64 のシンボリックリンク先を/usr/lib64 から/my-lib64 に変更したい

いったい何の役に立つねんシリーズ。技術の無駄遣い担当の平野です。
今回は、CentOS の/lib64 のシンボリックリンクをどう書き換えるかをご紹介します。

## はじめに

とあることから、システムの標準ライブラリ(.so)をすべて置き換えたくなりました。
/usr/lib64 にあるようなライブラリを全部一括で、です。
CentOS7 や 8 の場合、システムのライブラリは/lib64 から実体/usr/lib64 へのシンボリックリンクになっています。
新しいライブラリを置いたディレクトリへ/lib64 のシンボリックリンクを付け替えるのが楽そうです。

## やってみる

まず思いつくのは、以下のような方法です。
最初は気にせず、これを実行しました。

```console
# ln -sf /my-lib64 /lib64
```

しかし、あれれ。書き換わっていないようです。

```console
# ls -ld /lib64
lrwxrwxrwx 1 root root 9 May 11  2019 /lib64 -> usr/lib64
```

システムの部分なので、変更途中でエラーになって変えられないのかなぁ、とか考えながら、とりあえず、別の方法を試してみます。

※お急ぎの方は解決方法 ④ へどうぞ。

## 削除して作成 (失敗)

上書きができないのなら、簡単に思いつくのは、削除して、新たに作成するという方法です。
やってみましょう。

```console:CentOS7
# rm /lib64
# ln -s /my-lib64 /lib64
sh: /usr/bin/ln: /lib64/ld-linux-x86-64.so.2: bad ELF interpreter: No such file or directory
```

半分予想してはいましたが、システムのライブラリを削除してしまったので、ln コマンドが実行できなくなってしまいました。

では、LD_LIBRARY_PATH で指定してみたらどうでしょうか。

```console:CentOS7
# LD_LIBRARY_PATH=/usr/lib64 ln -s /my-lib64 /lib64
sh: /usr/bin/ln: /lib64/ld-linux-x86-64.so.2: bad ELF interpreter: No such file or directory
```

効かないですね。

もはや、ls コマンドさえ、もう実行できません。

```console:CentOS7
# ls
sh: /usr/bin/ls: /lib64/ld-linux-x86-64.so.2: bad ELF interpreter: No such file or directory
```

さようなら。。。

## /lib64 がない状態でコマンドを実行する

システムのライブラリが使えない状態でコマンドが実行できるようにするには、static link でコンパイルしたコマンドを利用することを、まず思いつきます。
しかし、残念ながら CentOS7 の ln コマンドは static link にはなっておらず、いくつかの外部ライブラリを必要としていました。

```console:CentOS7
# ldd /usr/bin/ln
        linux-vdso.so.1 =>  (0x00007ffeaa98e000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f48229de000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f4822dac000)
```

自分で static link されたコマンドをコンパイルするのは負けた気分がするので、これは、最後の手段とすることで、少し調査してみることにしました。

まずは、他力本願で、全世界の人々に頼ろうと、自分の Facebook で軽くつぶやいてみました。すると、すぐに会社の同僚から反応が来ました。

> LiveCD でやってみたら?

いやいやいやいやいやいや、それはダメです。最後です。負けです。ていうか、環境は Docker コンテナです。却下。

しばらくすると、予想もしていなかった、自転車仲間方面から反応がありました。

> ld-linux でライブラリ指定すれば?

ほぉぉぉ。なんですかね、それは。さっそく試してみましょう。

## 解決方法 ① ld-linux を使う

ld-linux はさっきから No such file or directory と怒られている張本人です。
それで、ライブラリを指定するとは、どういうことだろう、と少し調べてみると、なんと、そのまま実行できるようです。

```
# /usr/lib64/ld-linux-x86-64.so.2
Usage: ld.so [OPTION]... EXECUTABLE-FILE [ARGS-FOR-PROGRAM...]
  ... 長い説明 ...
  --list                list all dependencies and how they are resolved
  --verify              verify that given object really is a dynamically linked
                        object we can handle
  --inhibit-cache       Do not use /etc/ld.so.cache
  --library-path PATH   use given PATH instead of content of the environment
                        variable LD_LIBRARY_PATH
  --inhibit-rpath LIST  ignore RUNPATH and RPATH information in object names
                        in LIST
  --audit LIST          use objects named in LIST as auditors
```

おーーー。どうやら、--library-path というのでライブラリの場所を指定しつつ、コマンドを実行できるようです。
LD_LIBRARY_PATH はだめでしたが、こちらはどうでしょうか。

```console:CentOS7
# /usr/lib64/ld-linux-x86-64.so.2 --library-path /usr/lib64 /usr/bin/ln -s /my-lib64 /lib64
# ls -ld /lib64
lrwxrwxrwx 1 root root 9 Mar 25 02:42 /lib64 -> /my-lib64
```

すばらしい！！！
できました！！

## 解決方法 ② static link されたコマンドを作る

さて、もう、今なら負けた気分はしません。
勝ち誇った気持ちで、自作 static link コマンドバージョンも試しておきます。

こんなコードです。
ld-linux なんちゃらを調べるより速く書けます。

```C:my-ln.c
#include <unistd.h>

int main() {
    symlink("/my-lib64", "/lib64");
    return 0;
}
```

コンパイルします。

```console
# yum install -y gcc glibc-static
# gcc -Wall -static -o my-ln my-ln.c
```

試します。

```console
# rm /lib64
# ls -ld /lib64
sh: /usr/bin/ls: /lib64/ld-linux-x86-64.so.2: bad ELF interpreter: No such file or directory
# ./my-ln
# ls -ld /lib64
lrwxrwxrwx 1 root root 9 Mar 25 02:58 /lib64 -> /my-lib64
```

できました！！！

## 解決方法 ③ 用意された static link されたコマンドを使う

実は、悔しいので隠していたのですが、調べてる途中で OS に static link された ln コマンドが付属していることがわかりました。

90 年代頃は、sh やメンテに必要なコマンドが、/bin や/sbin の下に同名で static link された状態で置かれてしました。
最近は/usr/bin や/usr/sbin へのシンボリックリンクなので気づかなかったのですが、/sbin/sln というのがありました。

```console
# rm /lib64
# ls -ld /lib64
sh: /usr/bin/ls: /lib64/ld-linux-x86-64.so.2: bad ELF interpreter: No such file or directory
# sln /my-lib64 /lib64
# ls -ld /lib64
lrwxrwxrwx 1 root root 9 Mar 25 03:05 /lib64 -> /my-lib64
```

素晴らしい！

## 解決方法 ④ そもそも ln だけでできたのだ

そもそも、ことの発端は

```console
# ln -sf /my-lib64 /lib64
```

がなぜか効かなかったことでした。
問題があるのならエラーが出るはずで、エラーもなく静かに終わるのが少し気になります。

ということで、詳細を strace で追いかけてみることにしました。
何かエラーが出てるかもしれません。

```console
# strace ln -s /my-lib64 /lib64
execve("/usr/bin/ln", ["ln", "-s", "/my-lib64", "/lib64"], [/* 8 vars */]) = 0
... snip ...
symlink("/my-lib64", "/lib64/my-lib64") = 0
... snip ...
+++ exited with 0 +++
```

え゛っ???

/lib64 の下に my-lib64 のリンクを作ってるじゃないですか！！

```console
# ls -ld /usr/lib64/my-lib64
lrwxrwxrwx 1 root root 9 Mar 25 03:11 /usr/lib64/my-lib64 -> /my-lib64
```

確かにあります。。。

よく考えたら、cp も mv もこの動きですね。
対象がディレクトリへのシンボリックリンクだったので、置き換えではなく、そのディレクトリ配下にリンクを作ってしまっていた、と。

こういうとき、cp や mv コマンドには、対象がディレクトリの場合にもファイルのように扱うオプション「-T」があります。
ln コマンドもにもあるでしょうか。

```console
# ln --help
... snip ...
  -T, --no-target-directory   treat LINK_NAME as a normal file always
... snip ...
```

ありますねぇ。

試してみます。

```console
# ls -ld /lib64
lrwxrwxrwx 1 root root 9 Oct  1 01:15 /lib64 -> usr/lib64
# ln -sfT /my-lib64 /lib64
# ls -ld /lib64
lrwxrwxrwx 1 root root 9 Mar 25 03:18 /lib64 -> /my-lib64
```

なんと、簡単にできてしまいました。

## 終わりに

わかってみれば、簡単な話でした。。。
だいぶ、遠回りをしました。。。

こうして、技術の無駄遣い、いったい何の役に立つねんシリーズができあがっていくのです。


*本記事は @qualitia_cdevの中の一人、@hirachanさんに作成して頂きました。

## *いったい何の役に立つねんシリーズ
1. [CentOS で/lib64 のシンボリックリンク先を/usr/lib64 から/my-lib64 に変更したい](https://qiita.com/qualitia_cdev/items/0eb498b25a6760518e3c)
2. [AWS Lambda で実行できるコマンドを作成する環境を作ってみた](https://qiita.com/qualitia_cdev/items/4aebb1e22be9afab0a16)