[Linux] カレントユーザのホームディレクトリはどこから取得すべきか

（※タイトルの通り、本記事では Linux のみを対象として調査しています。Unix/非 Unix 問わず、それ以外の OS には当てはまらない部分が多いのでご注意ください）

## 答え

`$HOME` 環境変数を見ましょう。

Linux でのユーザ情報は「パスワードデータベース」（通常は `/etc/passwd` ファイル。以下、本記事では単に "passwd" と略称）と呼ばれるところに格納されており、そこにユーザごとのホームディレクトリ情報も記載されています。

しかし passwd の情報を取得する `getpwuid` (`getpwnam`) の MAN ページに下記の通り記載があります:

> アプリケーションが、ユーザーのホームディレクトリを決定する場合には、 (_getpwuid(getuid())->pw_dir_ の値ではなく) **HOME** の値を検査するようにすべきである。 なぜなら、このようにすることで、ユーザーがログインセッション中で 「ホームディレクトリ」の意味を変更できるようになるからである。

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getpwnam.3.html

最近、コンテナを実行する際に `$HOME` を上書きしている例をちらほら見かけたのですが、

- 値を設定するユーザとしてアプリケーションがその値をちゃんと見てくれると期待していいものなのか？（環境変数無視して別の値を見に行ってしまうアプリケーションはないのか？）
- アプリケーション開発者として `$HOME` の値を信頼してもいいものなのか？

ともやもやしていました。

結論、どちらも YES。ユーザは `$HOME` を上書きしていいし、アプリケーションはその値を正しい「ホームディレクトリ」として扱うべきということで、すっきりしました。

## 補遺 1: 誰が（最初に）`$HOME` を設定するのか

答え: システム（OS）のログイン処理が passwd に記載の値を設定する。

これまた先ほど引用した `man getpwnam` の一つ前の文に記載があります:

> ログインプロセスは、このフィールドの値を使って、 ログインシェルの **HOME** 環境変数を初期化する。

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getpwnam.3.html

また POSIX にも下記のような規定があります:

> The system shall initialize this variable at the time of login to be a pathname of the user's home directory. See [_\<pwd.h\>_](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pwd.h.html).
>
> （拙訳）
> システムが、ログイン時にこの変数をユーザのホームディレクトリのパス名に設定すべきである。[_\<pwd.h\>_](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pwd.h.html) 参照。

https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html

参考:

https://superuser.com/a/271935

https://unix.stackexchange.com/a/123859

## 補遺 2: 世の実装はどうなっているのか？

**_ここからは完全に趣味の領域で長くなります。結びの言葉もありませんので、興味がない方はここでそっといただけると。_**

いくつかの言語のライブラリやアプリケーションで、ホームディレクトリの取得処理がどうなっているのかを実際に調査してみました。

基本的には「`$HOME` 環境変数を見る、なければ passwd を見る」という処理が多いようですが、前者だけで済ませている例もありました。

また、空文字の環境変数が設定されていたときの挙動についても、実装により差が見受けられます。差異にも注意が必要ですが、それ以上に空文字がそのまま使われるシステムでは `~/.config` が `/.config` に展開されるということなので気をつける必要があります。

| 言語・ライブラリ                       | 環境変数 | 空文字の環境変数 | passwd | その他のフォールバック |
| :------------------------------------- | :------: | :--------------- | :----: | :--------------------- |
| Python `os.path`                       |    ✓     | 使われる         |   ✓    |                        |
| Ruby `Dir`                             |    ✓     | 使われる         |   ✓    |                        |
| Node.js `os`                           |    ✓     | 使われる         |   ✓    |                        |
| Go `os`                                |    ✓     | 使われ**ない**   |  :x:   |                        |
| Go `os/user`                           |   :x:    |                  |   ✓    |                        |
| Go `github.com/mitchellh/go-homedir`   |    ✓     | 使われ**ない**   |   ✓    | `sh -c 'cd && pwd'`    |
| Rust `dirs`/`directories` (`dirs-sys`) |    ✓     | 使われ**ない**   |   ✓    |                        |
| Rust `home`/`std::env`                 |    ✓     | 使われる         |   ✓    |                        |
| Rust `home`/`std::env`                 |    ✓     | 使われる         |   ✓    |                        |
| Bash `~` の展開                        |    ✓     | 使われる         |   ✓    |                        |
| Bash 引数なし `cd`                     |    ✓     | 使われる         |  :x:   |                        |
| Ash `~` の展開・引数なし `cd`          |    ✓     | 使われる         |  :x:   |                        |
| Git                                    |    ✓     | 使われる         |  :x:   |                        |

### Python: `os.path.expanduser('~')`

環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。

- :book: [os.path --- 共通のパス名操作 — Python 3.9.4 ドキュメント](https://docs.python.org/ja/3/library/os.path.html#os.path.expanduser)
- :octocat: [cpython/posixpath.py at v3.9.7 · python/cpython](https://github.com/python/cpython/blob/v3.9.7/Lib/posixpath.py#L228)

### Ruby: `Dir.home`

環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。

- :book: [Dir.home (Ruby 3.0.0 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/latest/method/Dir/s/home.html)
- :octocat: [ruby/file.c at v3_0_2 · ruby/ruby](https://github.com/ruby/ruby/blob/v3_0_2/file.c#L3600)

### Node.js: `os.homedir()`

環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。

- :book: [OS | Node.js v16.8.0 Documentation](https://nodejs.org/docs/latest-v16.x/api/os.html#os_os_homedir)
- :octocat: [node/core.c at v16.8.0 · nodejs/node](https://github.com/nodejs/node/blob/v16.8.0/deps/uv/src/unix/core.c#L1067)

### Go

#### `os.UserHomeDir()`

環境変数のみを見ます。空文字は未設定と同じ扱いになります。

- :book: [os package - os - pkg.go.dev](https://pkg.go.dev/os#UserHomeDir)
- :octocat: [go/file.go at go1.17 · golang/go](https://github.com/golang/go/blob/go1.17/src/os/file.go#L500)

#### `os/user`

`os.UserHomeDir()` は Go 1.12 で追加されたものですが、それ以前に標準パッケージを使う場合は `os/user` パッケージが利用されていたようです

ただしこれはあくまで passwd を扱うためのライブラリなので環境変数は特に見ませんし、CGO 必須でクロスコンパイルに向いていませんでした。

- :book: [user package - os/user - pkg.go.dev](https://pkg.go.dev/os/user@go1.17#Current)
- :octocat: [go/cgo_lookup_unix.go at go1.17 · golang/go](https://github.com/golang/go/blob/go1.17/src/os/user/cgo_lookup_unix.go#L50)

#### `github.com/mitchellh/go-homedir`

というわけで Go 1.12 以前では [`github.com/mitchellh/go-homedir`](https://github.com/mitchellh/go-homedir) パッケージが人気でした（ちなみに [@mitchellh](https://github.com/mitchellh) さんは Hashicorp の創業者であり、Terraform や Vagrant などを作った方ですね！）。

これは環境変数と passwd の両方を見るだけでなく、それでもダメなら `sh -c 'cd && pwd'` を実行するという処理が入っているのがユニークです（結局 `cd` コマンドは何を見るの？となるので、果たしてこのフォールバックに意味があるのかはよくわかりません）。空文字は未設定と同じ扱いになります。

- :book: [homedir package - github.com/mitchellh/go-homedir - pkg.go.dev](https://pkg.go.dev/github.com/mitchellh/go-homedir#Dir)
- :octocat: [go-homedir/homedir.go at v1.1.0 · mitchellh/go-homedir](https://github.com/mitchellh/go-homedir/blob/v1.1.0/homedir.go#L89)

### Rust

Rust は `std::env::home_dir()` という関数があるのですが、Windows 上での動作に問題があるとのことで現在は deprecated になっています。ですので、いくつか thrid-party の crate も合わせて確認してみましょう。

#### `dirs`/`directories` (`dirs-sys`)

[`dirs`](https://crates.io/crates/dirs) と [`directories`](https://crates.io/crates/directories) が一般開発者に使われるライブラリで、実際の処理が内部用ライブラリ [`dirs-sys`](https://crates.io/crates/dirs-sys) にて実装されています。

環境変数と passwd の両方を見ます。空文字は未設定と同じ扱いになります。

- :book: [home_dir in dirs - Rust](https://docs.rs/dirs/3.0.2/dirs/fn.home_dir.html)
- :book: [UserDirs in directories - Rust](https://docs.rs/directories/3.0.2/directories/struct.UserDirs.html#method.home_dir)
- :octocat: [dirs-sys-rs/lib.rs at v0.3.4 · dirs-dev/dirs-sys-rs](https://github.com/dirs-dev/dirs-sys-rs/blob/v0.3.4/src/lib.rs#L30)

#### `home`/`std::env`

[`home`](https://crates.io/crates/home) は [Cargo などで使われている](https://github.com/rust-lang/cargo/blob/0.55.0/Cargo.toml#L39) crate で、Unix 環境では[単に公式の `std::env::home_dir()` を使う実装になっています](https://github.com/brson/home/blob/v0.5.1/src/lib.rs#L70-L75)（前述の通り `std::env::home_dir` が deprected なのは Windows 上での動作が問題になったのであり、Unix では問題なく動作する）。

環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます（`home` crate のドキュメント上は「空文字は未設定扱い」となっていますが、間違っているようです。ref: [brson/home#22](https://github.com/brson/home/issues/22)）。

- :book: [home::home_dir - Rust](https://docs.rs/home/0.5.3/home/fn.home_dir.html)
- :book: [home_dir in std::env - Rust](https://doc.rust-lang.org/std/env/fn.home_dir.html)
- :octocat: [rust/os.rs at 1.54.0 · rust-lang/rust](https://github.com/rust-lang/rust/blob/1.54.0/library/std/src/sys/unix/os.rs#L581)

### Bash

#### `~` の展開

環境変数と passwd の両方を見ます。空文字でも設定されていればその値が使われます。

- :book: [Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Tilde-Expansion)
- Source: [tilde.c\tilde\lib - bash.git - bash](https://git.savannah.gnu.org/cgit/bash.git/tree/lib/tilde/tilde.c?h=bash-5.1#n336)

#### 引数なし `cd`

環境変数のみを見ます。空文字でも設定されていればその値が使われますが、それはつまり移動しないということです。

- :book: [Bash Reference Manual](https://www.gnu.org/software/bash/manual/bash.html#Bash-Builtins)
- Source: [cd.def\builtins - bash.git - bash](https://git.savannah.gnu.org/cgit/bash.git/tree/builtins/cd.def?h=bash-5.1#n320)

### Busybox Ash

`~` の展開、引数なし `cd` のいずれでも環境変数しか見ません。空文字でも設定されていればその値が使われます。

- :book: [ash(1): command interpreter - Linux man page](https://linux.die.net/man/1/ash)
- Source
    - 引数なし `cd`: [ash.c « shell - busybox - BusyBox: The Swiss Army Knife of Embedded Linux](https://git.busybox.net/busybox/tree/shell/ash.c?h=1_34_0#n2904)
    - `~` の展開: [ash.c « shell - busybox - BusyBox: The Swiss Army Knife of Embedded Linux](https://git.busybox.net/busybox/tree/shell/ash.c?h=1_34_0#n6513)

### Git

環境変数のみを見ます（`git config --global` は `$XDG_CONFIG_HOME` も見ます）。空文字でも設定されていればその値が使われます。

- :book: [Git - git-config Documentation](https://git-scm.com/docs/git-config#FILES)
- :book: [Git - Environment Variables](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables)
- :octocat:
    - `git config --global`: [git/path.c at v2.33.0 · git/git](https://github.com/git/git/blob/v2.33.0/path.c#L1506)
    - `~` の展開: [git/path.c at v2.33.0 · git/git](https://github.com/git/git/blob/v2.33.0/path.c#L728)