📦

WSL2とsystemdとnslogin

2022/07/20に公開

はじめに

こちらの記事でWSL2でsystemdを動かしました。
systemdが新規のPID namespaceで動いているために、systemctlなどのコマンドを打つためには/usr/libexec/nsloginコマンドを打ってそのnamespaceに入る必要がありました。
毎回nsloginを打つのは面倒くさいのと、そもそも普通のLinuxにはnsloginがないのでできれば同じように手動でnsloginせずにsystemctlしたいです。

https://zenn.dev/takai404/articles/9c96d5d1bcc9d0

結論

以下のどれかがいいと思います。

  • Microsoft StoreからUbuntuをインストール
    • 設定→アプリ→アプリと機能→その他の設定→アプリ実行エイリアスで新規インストールした方をONにする
    • 500MBほどディスク領域を余分に使う
  • C:\Windows\System32\wsl.exe -d Ubuntu --cd ~ -e /bin/bash -c /usr/libexec/nsloginで起動する
    • デスクトップなどにショートカットを置く、ランチャーソフト・Windows TerminalのWSL起動コマンドを書き換える必要あり
  • Microsoft StoreからUbuntu 22.04 LTSをインストール
    • 現在別のWSLインスタンスを使っている場合は作り直しになります。
    • 将来24.04にバージョンアップした後もWSLインスタンスの名前が「Ubuntu-22.04」のままです。

調べたこと

先人の知恵

こちらの記事によると以下のようになるらしい。

  • wsl -e /usr/libexec/nslogin /bin/bashで起動するとsystemd用のPID namespaceで入れる
    • ただし環境変数SHELLがnsloginになる
  • Ubuntu22.04だと問題ない

https://qiita.com/shigeokamoto/items/ca2211567771cf40a90d?utm_campaign=post_article&utm_medium=twitter&utm_source=twitter_share

環境変数SHELL

実際に試してみるとその通りで、wsl -e /usr/libexec/nslogin /bin/bashだと確かに環境変数SHELLが/usr/libexec/nsloginでした。

C:\>wsl -e /usr/libexec/nslogin /bin/bash
root@Win11:/mnt/c# echo $SHELL
/usr/libexec/nslogin

例えば、viでファイル編集中に:r! lsとかやってカレントディレクトリのファイルを挿入したりするときに、環境変数SHELLが適切に設定されていないと以下のエラーがでます。これは/usr/libexec/nslogin -c (ls)というコマンドを実行して、nsloginが-cオプションを受け付けないのでエラーになっています。

Executing "-c" failed: No such file or directory

shell returned 7

環境変数SHELLをbashにするのがいいんですが、どこでセットするかですよね?
.bashrcとかにexport SHELL=/bin/bashと書くのでもよいとは思うのですが、普通のLinuxだと.bashrcで環境変数SHELLをセットしたりしないのでなんとなく気持ちが悪いです。

そもそも誰が環境変数SHELLに/usr/libexec/nsloginをセットしているのか?

nslogin

最初はnsloginコマンドが環境変数SHELLを/usr/libexec/nsloginにセットしていると想像しました。
ただソースコードをざっとみても環境変数SHELLをセットしているところを見つけられないです。逆に環境変数SHELLから値を読みだしている処理はあります。nsloginに引数を与えない場合は環境変数SHELLの値をexecvpに渡してプログラム実行しています。

https://github.com/ubuntu/wsl-setup/blob/main/systemd/nslogin/nslogin.c#L123-L142

bash

nsloginがセットしているのではないとすると誰がセットしているのか?bashでしょうか?

man bashを見ると以下の記述があります。bash起動時に環境変数SHELLがセットされていない場合は現在のログインシェルのフルパスをセットするとのこと。

SHELL
This variable expands to the full pathname to the shell. If it is not set when the shell starts, bash assigns to it the full pathname of the current user's login shell.

ログインシェルは/etc/passwdに書かれてるシェルです。
普通は/bin/bashですよね。bashの処理で環境変数SHELLが/usr/libexec/nsloginになるとは考えづらいです。

# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash

実験

nsloginが起動した時点で環境変数SHELLに何が入っているのかを確かめるためにwsl -e /usr/libexec/nslogin /bin/bashの最後の引数を省略してwsl -e /usr/libexec/nsloginを実行してみます。

ps -efをするとおびただしい量のnsloginコマンドがPIDをインクリメントしながら起動されていることが分かります。

root      169942  169941  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169943  169942  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169944  169943  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169945  169944  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169946  169945  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169947  169946  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169948  169947  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169949  169948  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169950  169949  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169951  169950  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169952  169951  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169953  169952  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169954  169953  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169955  169954  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169956  169955  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169957  169956  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169958  169957  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169959  169958  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169960  169959  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169961  169960  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169962  169961  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169963  169962  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169964  169963  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169965  169964  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169966  169965  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169967  169966  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169968  169967  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169969  169968  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169970  169969  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin
root      169971  169970  0 21:02 pts/1    00:00:00 /usr/libexec/nslogin

さきほどのnsloginのソースコードを見ると引数がないときは環境変数SHELLのプログラムにexecvpしています。[1]
nsloginがnsloginを起動し続けることと、nsloginに環境変数SHELLをセットする処理が見当たらないことは、nslogin呼び出し前に環境変数SHELLが/usr/libexec/nsloginにセットされていることを示しています。

wsl.exe

以上から考えると、wsl.exeが環境変数SHELLに/usr/libexec/nsloginをセットしています。おそらく-eオプション(の最初の単語)を環境変数SHELLにセットしているのでしょう。

wsl.exe -eの次に/bin/bashが来れば、それが環境変数SHELLに入るだろうと思い、以下のコマンドを打つと環境変数SHELLが予想通り/bin/bashになりました。

C:\>wsl.exe -e /bin/bash -c /usr/libexec/nslogin
root@Win11:/mnt/c# echo $SHELL
/bin/bash
  1. wsl.exeは-eオプションに従い、環境変数SHELLに/bin/bashをセットして、/bin/bash -c /usr/libexec/nsloginを実行
  2. /bin/bashは-cオプションに従い、/usr/libexec/nsloginを実行し、そのコマンドが終了したらbashも終了する
  3. /usr/libexec/nsloginは環境変数SHELLの値を読みだしてその値(/bin/bash)をsystemdのPID namespaceにて実行

wsl.exeは-eオプションがない場合は、デフォルトユーザのホームディレクトリでデフォルトユーザのログインシェルを実行します。-eを付けると引数で指定したプログラムを実行するのですが、ユーザのホームディレクトリには移動しなくなります。
移動するためには--cd ~オプションを指定します。
この--cd-eより後ろに指定すると--cd ~-eのコマンドの一部だと認識するようで、思うようにディレクトリ移動しません。-eの前に--cd指定する必要があります。

C:\>wsl.exe -e /bin/bash -c /usr/libexec/nslogin --cd ~
root@Win11:/mnt/c# pwd
/mnt/c
root@Win11:/mnt/c# exit
exit

C:\>wsl.exe --cd ~ -e /bin/bash -c /usr/libexec/nslogin
root@Win11:~# pwd
/root

ショートカットで対処

以下のショートカットをデスクトップや自分の使っているランチャーソフトに登録することで明示的にnsloginしなくてもsystemctlなどのコマンドを実行できるようになります。「-d Ubuntu」のところはお使いのWSL環境に応じて変えてください。(一覧はwsl -lで出ます)

C:\Windows\System32\wsl.exe -d Ubuntu --cd ~ -e /bin/bash -c /usr/libexec/nslogin

Ubuntu22.04で対処

Ubuntu22.04なら難しいことを考えなくてもsystemd環境に入れるというのを試してみました。
Microsoft Storeには「Ubuntu 22.04 LTS」という名前のアプリがあります。

https://apps.microsoft.com/store/detail/ubuntu-2204-lts/9PN20MSR04DW?hl=ja-jp&gl=JP

これをインストール[2]して/etc/wsl.confにsystemdの設定をして再起動します。

/etc/wsl.conf
[boot]
command=/usr/libexec/wsl-systemd

スタートメニューから「Ubuntu 22.04 LTS」を選択するとあっさりとsystemd PID namespaceに入ることができました。(systemdのPIDが1になっているということはsystemdと同じPID namespaceにいることを意味しています)

$ pidof systemd
1

コマンドプロンプトなどから起動するときは「ubuntu2204」コマンドで起動できます。これもあっさりsystemd PID namespaceに入ることができました。

C:\>ubuntu2204
user01@DESKTOP-QUCRLP5:~$ pidof systemd
1

「ubuntu2204」コマンドがどこにあるのかというと以下にあります。

C:\>where ubuntu2204
C:\Users\user01\AppData\Local\Microsoft\WindowsApps\ubuntu2204.exe

ただこのファイルはサイズが0バイトです。
なんで0バイトのファイルが実行できるのかというと、NTFSのreparse pointとかいう機能らしい。

C:\>fsutil reparsepoint query %LOCALAPPDATA%\Microsoft\WindowsApps\ubuntu2204.exe
再解析タグ値 : 0x8000001b
タグ値: Microsoft

再解析データの長さ: 0x1ca
再解析データ:
0000:  03 00 00 00 43 00 61 00  6e 00 6f 00 6e 00 69 00  ....C.a.n.o.n.i.
0010:  63 00 61 00 6c 00 47 00  72 00 6f 00 75 00 70 00  c.a.l.G.r.o.u.p.
0020:  4c 00 69 00 6d 00 69 00  74 00 65 00 64 00 2e 00  L.i.m.i.t.e.d...
0030:  55 00 62 00 75 00 6e 00  74 00 75 00 32 00 32 00  U.b.u.n.t.u.2.2.
0040:  2e 00 30 00 34 00 4c 00  54 00 53 00 5f 00 37 00  ..0.4.L.T.S._.7.
0050:  39 00 72 00 68 00 6b 00  70 00 31 00 66 00 6e 00  9.r.h.k.p.1.f.n.
0060:  64 00 67 00 73 00 63 00  00 00 43 00 61 00 6e 00  d.g.s.c...C.a.n.
0070:  6f 00 6e 00 69 00 63 00  61 00 6c 00 47 00 72 00  o.n.i.c.a.l.G.r.
0080:  6f 00 75 00 70 00 4c 00  69 00 6d 00 69 00 74 00  o.u.p.L.i.m.i.t.
0090:  65 00 64 00 2e 00 55 00  62 00 75 00 6e 00 74 00  e.d...U.b.u.n.t.
00a0:  75 00 32 00 32 00 2e 00  30 00 34 00 4c 00 54 00  u.2.2...0.4.L.T.
00b0:  53 00 5f 00 37 00 39 00  72 00 68 00 6b 00 70 00  S._.7.9.r.h.k.p.
00c0:  31 00 66 00 6e 00 64 00  67 00 73 00 63 00 21 00  1.f.n.d.g.s.c.!.
00d0:  75 00 62 00 75 00 6e 00  74 00 75 00 32 00 32 00  u.b.u.n.t.u.2.2.
00e0:  30 00 34 00 00 00 43 00  3a 00 5c 00 50 00 72 00  0.4...C.:.\.P.r.
00f0:  6f 00 67 00 72 00 61 00  6d 00 20 00 46 00 69 00  o.g.r.a.m. .F.i.
0100:  6c 00 65 00 73 00 5c 00  57 00 69 00 6e 00 64 00  l.e.s.\.W.i.n.d.
0110:  6f 00 77 00 73 00 41 00  70 00 70 00 73 00 5c 00  o.w.s.A.p.p.s.\.
0120:  43 00 61 00 6e 00 6f 00  6e 00 69 00 63 00 61 00  C.a.n.o.n.i.c.a.
0130:  6c 00 47 00 72 00 6f 00  75 00 70 00 4c 00 69 00  l.G.r.o.u.p.L.i.
0140:  6d 00 69 00 74 00 65 00  64 00 2e 00 55 00 62 00  m.i.t.e.d...U.b.
0150:  75 00 6e 00 74 00 75 00  32 00 32 00 2e 00 30 00  u.n.t.u.2.2...0.
0160:  34 00 4c 00 54 00 53 00  5f 00 32 00 32 00 30 00  4.L.T.S._.2.2.0.
0170:  34 00 2e 00 30 00 2e 00  31 00 30 00 2e 00 30 00  4...0...1.0...0.
0180:  5f 00 78 00 36 00 34 00  5f 00 5f 00 37 00 39 00  _.x.6.4._._.7.9.
0190:  72 00 68 00 6b 00 70 00  31 00 66 00 6e 00 64 00  r.h.k.p.1.f.n.d.
01a0:  67 00 73 00 63 00 5c 00  75 00 62 00 75 00 6e 00  g.s.c.\.u.b.u.n.
01b0:  74 00 75 00 32 00 32 00  30 00 34 00 2e 00 65 00  t.u.2.2.0.4...e.
01c0:  78 00 65 00 00 00 30 00  00 00                    x.e...0...

IO_REPARSE_TAG_APPEXECLINK
0x8000001B
Used by Universal Windows Platform (UWP) packages to encode information that allows the application to be launched by CreateProcess. Server-side interpretation only, not meaningful over the wire.

reparse pointのAPPEXECLINKという種類でプロセス起動の時の情報をもっているとのこと。うーん、結局symbolic linkみたいなものととらえればよいのか???

こちらに実体があるけど結局exeファイルなのでこれ以上解析できないですね。[3]

C:\Program Files\WindowsApps\CanonicalGroupLimited.Ubuntu22.04LTS_2204.0.10.0_x64__79rhkp1fndgsc\ubuntu2204.exe

一方でwslコマンドから起動するとデフォルトのPID namespace(=非systemd PID namespace)で起動します。

C:\>wsl -d Ubuntu-22.04
user01@DESKTOP-QUCRLP5:/mnt/c$ pidof systemd
19

-eで明示的にnsloginを指定すると、冒頭のUbuntuと同じようにsystemd PID namespaceに入れます。

C:\>wsl -d Ubuntu-22.04 --cd ~ -e /bin/bash -c /usr/libexec/nslogin
user01@DESKTOP-QUCRLP5:~$ pidof systemd
1

おそらくUbuntu-22.04のイメージにsystemd PID namespaceに入る仕組みがあるのではなくてubuntu2204.exeバイナリにその仕組みがあるのでしょう。

起動コマンドの違い

Microsoft StoreのUbuntu

ところで、こちらの記事にあるようにwsl --install -d ubuntuでUbuntu(20.04だったのを22.04にアップグレード)をインストールした環境でも、Microsoft StoreではUbuntuが「入手」になっています。もう入っているのだから「インストール済み」になるのでは?と思いつつも押してみます。

500MB程度のファイルをダウンロードします。
完了しました。(「開く」は押さなくてもいいです)

スタートメニューが「Ubuntu on Windows」だったのが

「Ubuntu」が増えています。

「Ubuntu」で起動したものが左、「Ubuntu on Windows」で起動したものが右です。新しくMicrosoftからインストールした「Ubuntu」はsystemd PID namespaceに入っています。localhost 123番ポート同士で通信できていることからわかるように、どちらも同じLinuxインスタンスに入っています。

wsl.exeで見てもLinuxインスタンスは1個しか動いていないです。

C:\>wsl -l -v
  NAME      STATE           VERSION
* Ubuntu    Running         2

コマンドプロンプトで「ubuntu」コマンドを打つとデフォルトのPID namespaceです。おそらく「Ubuntu on Windows」アプリが起動されているんでしょう。

C:\>where ubuntu
C:\Users\user01\AppData\Local\Microsoft\WindowsApps\ubuntu.exe

C:\>ubuntu
root@Win11:~# pidof systemd
16

設定→アプリ→アプリと機能→その他の設定→アプリ実行エイリアス がMicrosoft Storeの「Ubuntu」インストール前がこうだったのが

こうなっています。「Ubuntu」が増えました。

「Ubuntu」をオンにすると「Ubuntu on Windows」が自動的にオフになります。

C:\>where ubuntu
C:\Users\user01\AppData\Local\Microsoft\WindowsApps\ubuntu.exe

C:\>ubuntu
root@Win11:~# pidof systemd
1

コマンドプロンプトで「ubuntu」コマンドを打つとsystemdのPID namespaceに入るようになりました。おそらく「Ubuntu」アプリが起動されているんでしょう。

アプリ実行エイリアスを切り替える前後でreparse pointの指す先も変わっています。

インストール時期による違い

先の例では以下の時期にインストールしました。
2022/05にwsl --install -d ubuntuでインストール:「Ubuntu on Windows」
2022/07にMicrosoft Storeでインストール:「Ubuntu」

実験のために別環境でインストールしてみたところ、どちらも「Ubuntu」で同名になりました。orz
2022/07にwsl --install -d ubuntuでインストール:「Ubuntu」
2022/07にMicrosoft Storeでインストール:「Ubuntu」

名前は同じですが、アプリとしては別物のようで、それぞれオン・オフできます。

Microsoft Storeの「Ubuntu」(四角アイコン)とwsl --installコマンドの「Ubuntu」(丸アイコン)での違い。

どうもwsl --install -d ubuntuでインストールするものは時期によって名前が変わる模様。ただ、2022/07の時点ではMicrosoft Storeのubuntu.exeで起動したものだけがsystemd PID namespaceに入る機能を持っている模様。
もはやカオス。

まとめ

冒頭の結論を再掲

  • Microsoft StoreからUbuntuをインストール
    • 設定→アプリ→アプリと機能→その他の設定→アプリ実行エイリアスで新規インストールした方をONにする
    • 500MBほどディスク領域を余分に使う
  • C:\Windows\System32\wsl.exe -d Ubuntu --cd ~ -e /bin/bash -c /usr/libexec/nsloginで起動する
    • デスクトップなどにショートカットを置く、ランチャーソフト・Windows TerminalのWSL起動コマンドを書き換える必要あり
  • Microsoft StoreからUbuntu 22.04 LTSをインストール
    • 現在別のWSLインスタンスを使っている場合は作り直しになります。
    • 将来24.04にバージョンアップした後もWSLインスタンスの名前が「Ubuntu-22.04」のままです。

設定→アプリ→アプリと機能 で見てもwsl --install -d ubuntuとMicrosoft Storeのものは別物なんですよね。これからインストールするひとはMicrosoft Storeの「Ubuntu」をインストールするのが安全かもしれない。(ただ、Windowsの機能の有効化(仮想マシンプラットフォーム、Linux用Windowsサブシステム)、最新カーネルのダウンロード、WSL ver2をデフォルトにする、という処理を自動でやってくれるのかどうかは疑問)


https://apps.microsoft.com/store/detail/ubuntu/9PDXGNCFSCZV?hl=ja-jp&gl=JP

すでに使用中のUbuntuがある場合は、500MBの容量は無駄ですが1番目の方法がいいかと思います。
できるだけ今の環境を壊したくないとか、exeバイナリが何をしているかわからないというブラックボックスを排除したい場合は2番目ですね。

参考文献

https://qiita.com/shigeokamoto/items/ca2211567771cf40a90d?utm_campaign=post_article&utm_medium=twitter&utm_source=twitter_share

https://stackoverflow.com/questions/58296925/what-is-zero-byte-executable-files-in-windows

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4


2022/07/20追記:補足書きました。
https://zenn.dev/takai404/articles/cec14853bb027a

脚注
  1. execvpの前にforkしているので新規プロセスを起動し続けています。 ↩︎

  2. 日本語が文字化けしているので勘でボタンを押していきながらインストールします。 ↩︎

  3. アクセス権もないので無理やり所有者変えるとかしないとフォルダの中身自体も見えないし。 ↩︎

Discussion