TenForward

技術ブログ。はてなダイアリーから移転しました

5.11 で追加された OverlayFS の非特権マウント(1)

5.11 カーネルで overlayfs に大きな変更があったようで、久々にカーネルの新しい機能を試してみました。

とは言っても、結果だけ言うとすぐに終わってしまうので、すごいことをやったように見せかけるために、復習したりして順に説明していきましょう。時間のない方は最後の方だけ見れば良いです。

OverlayFS とは

Union Filesystem の実装の1つで、ディレクトリを重ね合わせて1つのディレクトリツリーを構成できます。Docker なんかではおなじみの機能ですね。おなじみの機能とはいえ、実際に直接マウントして動きを見たことがない方も多いかと思います。そこでまずは動きを簡単に見てみましょう。

重ね合わせるということで、下層側ディレクトリ、上層側ディレクトリを重ね合わせて、マウントポイント以下に見せます。他にワーク用の workdir として指定するディレクトリが必要です。

次の例では

  • 下層用ディレクトリとして lower
  • 上昇用ディレクトリとして upper
  • workdirとして work
  • マウントポイントとして overlay

というディレクトリを準備しています。lowerupper の中にはディレクトリとファイルを作成しておき、マウント後に overlay 以下にそれらのファイル・ディレクトリが見えることが確認できます。

$ mkdir lower upper work overlay # overlayfs用のディレクトリの作成
$ mkdir lower/lowerdir upper/upperdir # 下層、上層それぞれにディレクトリ作成
$ touch lower/lowerdir/lowfile upper/upperdir/upfile # ディレクトリ内にファイル作成
$ sudo mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
$ find overlay/
overlay/
overlay/lowerdir
overlay/lowerdir/lowfile
overlay/upperdir
overlay/upperdir/upfile
$ grep overlay /proc/$$/mountinfo 
68 33 0:67 / /home/karma/tmp/overlay rw,relatime - overlay overlay rw,lowerdir=lower,upperdir=upper,workdir=work

詳しくは連載記事 gihyo.jp や、カーネル付属文書 www.kernel.org をご覧ください。

他に関連記事としてこんな記事も書いています。

tenforward.hatenablog.com

5.11 カーネルで行われた非特権マウントのための変更と FS_USERNS_MOUNT

User Namespace内は、Namespace内では特権ユーザー、Namespace外では一般ユーザーという UID/GIDマッピングができる機能です。Namespace 内では特権を持つユーザーであっても、実際は特権を持たないユーザーでの処理がされているため、当然ながら一般的には User Namespace 内ではマウント操作はできません。

User Namespace について詳しくは連載の次の記事をご覧ください。

gihyo.jp

しかし、一部のファイルシステムについては、従来から User Namespace 内でマウントできました。例えば、コンテナ内で /proc や tmpfs などをマウントする操作は普通に行われている操作ではないかと思います。

このような User Namespace 内でファイルシステムをマウントできる機能は、非常に簡単な定義を行うだけで使えます。このためのファイルシステムに定義する定数が include/linux/fs.h に定義されています。

struct file_system_type {
        const char *name;
        int fs_flags;
#define FS_REQUIRES_DEV         1 
#define FS_BINARY_MOUNTDATA     2
#define FS_HAS_SUBTYPE          4
#define FS_USERNS_MOUNT         8       /* Can be mounted by userns root */
#define FS_DISALLOW_NOTIFY_PERM 16      /* Disable fanotify permission events */
  : (snip)

この FS_USERNS_MOUNT というのがそれで、ファイルシステムを実装する際にこの値を fs_flags に設定すると、コメントにあるように User Namespace 内の root が、そのファイルシステムをマウントできるわけです。

実は LXC 方面で使っていたため、Ubuntuカーネルにはこれまでも User Namespace 内で overlayfs をマウントするパッチが適用されていました(筆者がメンテナをつとめる Plamo Linux でも一時期適用されていたはずです)。

今回(5.11 カーネル)の OverlayFS の非特権マウントのパッチも非常に単純で、次のようなパッチです。これまで Ubuntu カーネルに適用されていたパッチも同じものです。

--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -2096,6 +2096,7 @@ static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags,
 static struct file_system_type ovl_fs_type = {
    .owner      = THIS_MODULE,
    .name       = "overlay",
+   .fs_flags   = FS_USERNS_MOUNT,
    .mount      = ovl_mount,
    .kill_sb    = kill_anon_super,
 };

ovl: unprivieged mounts

今回の OverlayFS に対するパッチは 10 個ほどのパッチとなっていますが、「非特権マウント」のために必要な変更は上記の変更だけです。他はより安全に処理を行うための修正のようで、今回だけでなく 5.8 でも変更が行われていたようです。

5.11 カーネルでの非特権 OverlayFS マウント

それでは先の例と同じディレクトリ、ファイルを使って非特権 OverlayFS を試してみましょう。使用するカーネルは 5.11.5 です。

$ uname -r
5.11.5-plamo64

「非特権」と言っても、先に説明したとおり「User Namespace 内の root がマウントできる」ということですので、unshare コマンドで User Namespace を作成して試します。(いずれにせよ mount コマンドは root でないと実行が失敗するようになってます)

ただ、ここで User Namespace だけを作ってもマウントは失敗します。

$ unshare --user --map-root-user
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
mount: /home/karma/tmp/overlay: permission denied. (失敗した)

これは Mount Namespace も元の Namespace とも独立している必要があるためです。

そこで次の例では unshare コマンドに --mount も指定して User/Mount Namespace を作成してみましょう。--map-root-user は unshare を実行するユーザーと User Namespace 内の root をマッピングするオプションです。次の例だと元の Namespace の UID: 1000 のユーザーと作成する Namespace 内の UID:0 をマッピングするということです。

$ id -u (現在のユーザーは UID:1000)
1000
$ unshare --user --map-root-user --mount (User Namespace と Mount Namespace を作成する)
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
# grep overlay /proc/self/mountinfo (マウント情報を確認する)
119 109 0:62 / /home/karma/tmp/overlay rw,relatime - overlay overlay rw,lowerdir=lower,upperdir=upper,workdir=work,index=off,metacopy=off
# find overlay/ (重ね合わせた状態でマウントできている)
overlay/
overlay/lowerdir
overlay/lowerdir/lowfile
overlay/upperdir
overlay/upperdir/upfile

マウントが成功しましたね。所有権も見ておきましょう。

# ls -l overlay/
合計 0
drwxr-xr-x 1 root root 14  314日  21:07 lowerdir/
drwxr-xr-x 1 root root 12  314日  21:07 upperdir/
# ls -l overlay/*
overlay/lowerdir:
合計 0
-rw-r--r-- 1 root root 0  314日  21:07 lowfile

overlay/upperdir:
合計 0
-rw-r--r-- 1 root root 0  314日  21:07 upfile

これらのファイルは元の Namespace のユーザー(UID: 1000)権限で作成しましたので、ちゃんと User Namespace 内でマウントしてもマッピング先のユーザー(UID: 0=root)の所有権になっています。

5.11 より前のカーネルでの実行例

一応、比較のために 5.11 より前のバージョンのカーネルで非特権マウントができないことも確認しておきましょう。ちょっと古いのですが、手元にあった 5.2 カーネルの環境で試してみました。

$ uname -r
5.2.1-plamo64
$ id -u
1000
$ unshare --mount --user --map-root-user
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
mount: /home/karma/tmp/overlay: permission denied.

同様に実行してみました。失敗しましたね。

(つづく)

マウントプロパゲーション(8)〜 Namespaceとプロパゲーション(2)〜

これまでの続きです。まだまだ続きます。

これまでと同様に完全に私個人が理解するための資料です(man 7 mount_namespaceみれば書いてますし)。間違いの指摘は大歓迎です。

前回と同様に引き続きマウントプロパゲーションとMount Namespaceの関係も見ていきます。今回はslaveです。

shell_1# mount -t tmpfs tmpfs /mnt_shrd/
shell_1# mount -t tmpfs tmpfs /mnt_mstr/
shell_1# mount --make-shared /mnt_shrd/ (sharedに設定)
shell_1# mount --make-shared /mnt_mstr/ (sharedに設定)
shell_1# grep /mnt /proc/self/mountinfo (sharedになっていることを確認)
590 31 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
604 31 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw

tmpfsをふたつマウントし、両方ともsharedに設定しました。mountinfoファイルを確認するとsharedとなっているのがわかります。

ここで別のシェル(shell_2)を起動し、新しくMount Namespaceを作成してみましょう。

shell_2# unshare --mount --propagation unchanged /bin/bash (Mount Namespaceを作成)
shell_2# grep /mnt /proc/self/mountinfo (sharedであることを確認)
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw

--propagation unchangedを指定していますので、さきほど行ったマウントはいずれもsharedで元のNamespaceと変わりません。ここでこのうちのひとつのマウントをslaveに設定してみましょう。

shell_2# mount --make-slave /mnt_mstr/ (slaveに設定)
shell_2# grep /mnt /proc/self/mountinfo (設定した方のマウントがslaveになったことを確認)
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime master:331 - tmpfs tmpfs rw

ここでは/mnt_mstrの方をslaveに設定しましたので、オプショナルフィールドはmaster:331となっており、もとのMount Namespaceでshared:331となっていたマウントのslaveになっていることがわかります。

この新しいMount Namespace内で、このふたつのマウントのサブマウントを作成し、mountinfoファイルを確認してみましょう。

shell_2# mkdir /mnt_shrd/a
shell_2# mkdir /mnt_mstr/b
shell_2# mount -t tmpfs tmpfs /mnt_shrd/a
shell_2# mount -t tmpfs tmpfs /mnt_mstr/b
shell_2# grep /mnt /proc/self/mountinfo (mountinfoを確認)
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime master:331 - tmpfs tmpfs rw
679 677 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw
694 678 0:57 / /mnt_mstr/b rw,relatime - tmpfs tmpfs rw

もともとマウントされていた/mnt_shrd/mnt_mstrは変わりません。そして新たに行ったサブマウントは、

  • /mnt_shrd/aは新たなsharedマウントでIDが338(親のマウントからsharedを継承)
  • /mnt_mstr/bはprivateマウント

となっていることがわかります。

最初のシェル(shell_1)に戻ってmountinfoファイルを確認してみると、

shell_1# grep /mnt /proc/self/mountinfo 
590 31 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
604 31 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw
680 590 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw

別のシェル(shell_2)で作成したMount Namespace内で行ったsharedマウントは、もとのMount Namespaceにも伝播しています。いっぽうでslaveに設定したほうのマウントは伝播していません。

ここで最初のシェル(shell_1)の方で新たにマウントを行ってみましょう。

shell_1# mkdir /mnt_shrd/c
shell_1# mkdir /mnt_mstr/d
shell_1# mount -t tmpfs tmpfs /mnt_shrd/c
shell_1# mount -t tmpfs tmpfs /mnt_mstr/d
shell_1# grep /mnt /proc/self/mountinfo 
590 31 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
604 31 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw
680 590 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw
695 590 0:58 / /mnt_shrd/c rw,relatime shared:345 - tmpfs tmpfs rw (←新たに行われたマウント)
747 604 0:59 / /mnt_mstr/d rw,relatime shared:352 - tmpfs tmpfs rw (←新たに行われたマウント)

あらたにIDが345352のsharedマウントが行われています。これを別のシェル(shell_2)のMount Namespace内で確認してみましょう。

shell_2# grep /mnt /proc/self/mountinfo 
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime master:331 - tmpfs tmpfs rw
679 677 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw
694 678 0:57 / /mnt_mstr/b rw,relatime - tmpfs tmpfs rw
696 677 0:58 / /mnt_shrd/c rw,relatime shared:345 - tmpfs tmpfs rw
748 678 0:59 / /mnt_mstr/d rw,relatime master:352 - tmpfs tmpfs rw

最初のシェル(shell_1)で行われたID 345352のマウントは両方とも伝播しており、slaveマウントのサブマウントのほうはmasterとなっていますので、ID 352に対するslaveマウントであることがわかります。

Huawei Watch GT 2e

5 月末に Huawei Watch GT 2e を買いました。Huawei Watch 2(GT2じゃないよ)からの買い替えです。ソレ以来毎日使っているのでレビューでも。

使い方を間違っていて的外れな感想が書いてあるかもしれません。その場合は教えて! 気づいた所は追記していきます。

f:id:defiant:20200527005444j:plain
Huawei Watch GT2e

WearOS から独自 OS 搭載へ変わりましたが、おおむね満足しています。とりあえず満足している点、不満な点なんかを紹介できれば。

選択した理由

スマホとの一体感を得るのなら WearOS か Apple Watch なのでしょうが、時計、ワークアウトの記録、文字盤のカスタマイズという所に主眼を置いている私には、

  • WearOS 搭載の製品は CPU がそんなに進化してないらしいということ(前の Watch 2 は動作がもっさりしてることがあるところ気になった)
  • スマホAndroid メイン

というわけで、WearOS 搭載製品と迷ったのですが、バッテリーの持ちがすごいとか、ワークアウト記録も良さそうでしたので、これにしてみました。Apple Watch は新しくなって色々高機能になっているようですが、スマートウォッチ単体の性能としてはそれぞれ長所短所がありそうですね。

セットアップ

スマートウォッチとしての評価が良くても、ケチが付きそうなのがココ。スマホiPhone だったら Apple Store からスマートウォッチを使用する際に必要なアプリを取得できるのですが、Android の場合、GT2e をスマホとペアリングして使う場合は

が必要です。しかし、現時点(2020-05末時点)では、Google Play Store からダウンロードできるバージョンは若干古いバージョンで、Android 10 には対応していないようです。ただし、Huawei 製のスマホであれば別です(調べたわけではないので知らんけど)。

となると、"Huawei App gallery" アプリを Web サイトから直接野良アプリとしてインストールして、前述のふたつを App Gallery 経由でインストールする必要があります。

ちょっと誰にでも勧められる感じではないですよね。

(参考)

covacova.hatenablog.com

バッテリーの持ち

バッテリーが 2 週間持つという売り込みと前評判でした。まあ実際使うとそれなりにヘヴィーな使い方なのでそこまではいかないものの、おおむね満足しています。

とりあえずこの前評判がどんなもんか?ということで届いてから 100% に充電したあと、あえて充電せずに使ってみました。

  • 入浴時以外は 24 時間装着
  • (このご時世なので)1 日在宅勤務でデスクワーク
  • 平日は毎日 30 分ほど、「屋外ランニング」モードでランニング
  • 休日は 2 時間ほど「屋外ウォーキング」でウォーキング
  • 待ち受け時文字盤は「なし」で、腕を持ち上げたときのみ ON する設定

これで 1 週間ほど使ってみて、大体 20% 程度の残量でした。2 週間はいかないものの、これだけ持てば十分です。"Huawei Watch 2" はワークアウトモードなしで大体 2 日で同じくらいの残量でしたので比べ物にならないくらいスタミナがあります。毎日 GPS オンにしないと本当に 2 週間充電なしでいけるかも?という予感はあります。

その後は大体入浴時だけ充電していますが、前述と同じ感じの運用で、平日 1 日装着したままで大体 10% 程度バッテリーが減る感じでしょうか(待ち受け文字盤を表示しない設定の時)。

最近は常時文字盤点灯で使っています。すると 1 日装着したまま、30分程度運動すると 85% 程度残量がある感じです。途中でGPS有効にして2.5時間ほど散歩した際は 1 日使った後は 100% → 80% 程度の電池の消費でした。若干、文字盤を消した運用よりは消費が増えますが、それでも十分満足の出来ですね。

画面

日が照ってる明るい状況でも見やすいです。

自分の持ってる画像をバックグラウンドに設定した文字盤が使えるので満足ですね。実はこれがスマートウォッチを使う理由のひとつでもあります。ウォッチフェイスを推しの画像にすることw

待ち受け文字盤

デフォルトでは通常は消灯で、腕を上げると画面が点灯するというスマートウォッチでは良くある設定です(「待受の文字盤」が「なし」という設定)。

でもこの腕を上げると点灯するって設定、実際は不便ではないですか?

私は通常は PC 前で作業を行うエンジニアですが、この場合、常時時計の文字盤が上を向いた状態で作業をしています(キーボード打ってるので)。 この設定のまま、文字盤を点灯させようとすると、

  • 一度腕を下ろして再度腕を持ち上げる
  • ボタンを押す

のどちらかが必要なようです。ここで画面をタップしたら点灯してくれれば、待ち受け文字盤を表示しない設定のままでも良いのですが…

というわけでここは待ち受け文字盤を表示させる設定です。待ち受け文字盤を表示させていれば、画面タップで通常の文字盤が表示されますので、推しの顔も確認できるというものです\(^o^)/

ワークアウト

WearOS 時の Google Fit のほうが良かったかな。

有酸素運動

おおむね満足です。

最近 30 分程度(5km)ランニングすることが多いのですが、脈拍数の上限を設定しておくと、上限に達したときに通知が来るので、そのときはペースを落としたりして調整できます。

有酸素系の運動をするときは色々な指標を記録してくれますし、リアルのランニング、ウォーキングは特に色々な記録をしてくれるので後で見返す際も自分のやった運動が振り返られて良いです。

f:id:defiant:20200609235818j:plain
ワークアウト

ただし、運動の自動検出をするという機能はちょっと使えない感じです。家から駅まで歩くのに20分くらい歩きますが、ほとんど駅に着く直前に「ワークアウトをオンにするか?」というような質問が表示されて、そこでオンにすればそこから計測が始まる感じです。到着寸前なのでオンにしたことないのでわかりませんが。

WearOS で Google Fit の場合は、スマホ本体も含めて常時 GPS で移動をウォッチしていて、自分で Google Fit アプリを起動して「屋外ウォーキング」や「屋外ランニング」を選択しなくても、精度は落ちるものの Google Fit に運動として記録されていました。

しかし Huawei Watch + Health だと自分で「これからワークアウトするよ」と教えないと記録の対象にならない感じです。

例えばランニングに行く時に「屋外ランニング」をオンにし忘れて(あるあるですよね?)途中でオンにした場合でも Google Fit であれば合算して通算で記録してくれますが、Huawei Health だと途中でオンにしたところからの記録になるので、やっぱりこの辺りを自動で検出して通算してくれるのでないと使えない感じです。そういう機能を期待してたのに、問い合わせしてくる機能とは(検出してバイブレーションで通知が来ても気づかないこともあるし)。ランニングの場合はもう少し早く運動を検知してくれるのかもしれませんが。

スマホも持ってランニングに行ってますので、相変わらず Google Fit では精度が悪い記録だけはされていっています。

筋力系

逆にジムでマシンを使った筋トレをする場合は圧倒的に Wear OS の Google Fit が良かったです。

まず、マシンを使う前に筋トレを始めることをスマートウォッチに教えると、腕の動きから「どのマシンをやってるか?」を自動判別して、候補を表示してくれます。(腕が動かないマシンだと検出出来ず自分で選択する必要ありましたがw)

GT2e は「筋トレ」というメニューがひとつあるだけなのでどういうメニューをやったかを後で振り返ることはできません。

そして Google Fit では運動を開始、終了したことを教えると、前述のようにメニューは自動認識して、そのメニューを何回やったかを記録してくれます(修正も可能)。そしてメニューを一旦終えると「インターバル」となって、あらかじめ設定した休憩時間をカウントしてくれます。

つまり、例えば「チェストプレス」と「ショルダープレス」をやるとすると、

  • チェストプレス15回 → インターバル 30 秒 → ショルダープレス15回 → インターバル 30 秒 → チェストプレス 15 回

みたいにインターバルを入れた筋トレが行えます。

GT2e では単に時間がカウントされてるだけで「インターバル」の考え方がないので、自分で時計とにらめっこしながら 30 秒休憩を行わないとダメです。

記録も WearOS + Google Fit だとこんな感じに細かくメニューまで記録されます。

f:id:defiant:20200705232426j:plain

WearOSだと筋力トレーニング何分何カロリーという記録しかされません。

f:id:defiant:20200705232657j:plain

バンド

バンドは「グリーン&ブラックTPUストラップ」を選択しました。色は気に入ってますが、実は購入直後 1 週間経たない間で黄色く変色した部分が現れました。

f:id:defiant:20200608112520j:plain
変色したバンド

これはあかんやろ、と思ってサポートに連絡すると

  • 送付もしくはリアルのサポートセンターに持ち込んでくれたら交換する
  • バンドは交換部品としてはないし、バンドのみの販売はないので本体ごと交換となる
  • 交換してまた変色したらまた相談してほしい
  • バンドのみの交換はできないし、別の色のバンドに交換はできない

ということでした。サポートセンターに持ち込んで交換してもらったので対応には不満はありません。しかし、

  • 常時装着している時計のバンドなので何もしなくても汚れる
  • 汚れたら交換したいと思うのはユーザーとしては普通の考え方だと思う
  • でも交換部品としてバンドを準備していないし、オプションとしてバンドのみの販売はない

これは消耗品としてのバンドを持ってるスマートウォッチではどうなんでしょ? ここは不満ですねえ。

交換して 1 ヶ月ほど使ってますが、今の所変色はありません。色が移りそうなところにはこすらないように気をつけたりしているのが関係しているのかしてないのかはわかりませんが…

通知

通知はスマホ本体の通知をそのまま GT2e に通知として送っているだけです。なので特に不足はないのですが、余計な通知まで送られるので邪魔な部分もあります。例えば

  • Twitter クライアントからツイートすると送信が済むまで「ツイートしています」というような通知が現れます。これがそのまま通知として腕に通知が来るので「知ってるわ!」となります(自分でツイートしてるのでw)
  • 音楽プレーヤーの曲が変わったり再生を停止したりするとその通知が来ます。これも不要ですね

通知のバイブレーションの強さは Huawei Watch 2 に比べると少し弱いかなあと思いますが、特に不満はありません。何かに集中してたり、その時の腕の角度なんかにもよるのかもしれませんが、たまに気づかないことがあります。

音楽プレーヤー

f:id:defiant:20200705234219j:plain

こんな感じ。あまりスマートウォッチでプレーヤーは操作しないので特に不満はないのですが、たまに再生中の曲の検出に失敗するのか何も再生していないことになります。曲名を確認したいことはあるのでそこだけが不満ですが、これはスマートウォッチでなくスマホ側のアプリ(HMS CoreとHealth)の問題じゃないですかね。

Huawei Health と Google Fit との同期

Health アプリは Google Fit との同期機能があります。

前述のようにスマホを常に持っていれば、精度は悪いにしても「ワークアウトする」と教えなくても Google Fit は記録を行ってくれます。

つまりは Health が Fit にデータ同期を行ってくれれば、Fit 自身の記録とマージしてくれて、ちゃんとした記録ができるのでは?と期待したのですが、今の所期待はずれです。

同期機能が何を同期するのか?についての説明を見たことがないのでわからないのですが、手元で見る限りは「睡眠時間」だけが同期されているように見えます。運動の記録とかも同期してくれよ。

関係ないのですが、この Huawei Health アプリ、Health 関係だけでなくデバイス(スマートウォッチ)の管理(文字盤の変更やOSアップデートなど)も請け負っていて、全然 Health アプリじゃないですw(Health 管理機能も持ってるので間違ってはないけど)

まとめ

色々満足な点、不満な点を書きましたが、とりあえず毎日装着して馴染んできていますので、毎日そこに装着されているものとして使えています。取り替えるほどの不満もないので、このまま不満な所はそのままスルーしながらしばらく使っていくんだと思います(前のやつは 2.5 年程度使った)。

マウントプロパゲーション(7)〜 Namespaceとプロパゲーション(1)〜

これまでの続きです。まだまだ続きます。

これまでと同様に完全に私個人が理解するための資料です(man 7 mount_namespaceみれば書いてますし)。間違いの指摘は大歓迎です。

マウントプロパゲーションとMount Namespaceの関係も見ておきましょう。

今回はprivateとsharedの場合にMount Namespaceが別になるとどうなるかを見てみます

次のようにprivateなマウントである/mnt_privとsharedな/mnt_shrdを準備します。以下の実行例ではふたつのシェルを使いますので、shell_1shell_2とプロンプトを変えています。

shell_1# mkdir /mnt_priv
shell_1# mkdir /mnt_shrd
shell_1# mount -t tmpfs tmpfs /mnt_priv/
shell_1# mount -t tmpfs tmpfs /mnt_shrd/
shell_1# mount --make-private /mnt_priv/ (privateに設定)
shell_1# mount --make-shared /mnt_shrd/  (sharedに設定)

ここでmountinfoファイルを確認しておきましょう。

shell_1# grep /mnt /proc/self/mountinfo 
325 31 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw
382 31 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw

この例ではmnt_shrdの方はIDが242のsharedとなっています。いっぽうで/mnt_privの方はエントリーがありませんのでprivateであることがわかります。

ここで別のシェルを開いてMount Namespaceを作成してみましょう。

shell_2# unshare --mount --propagation unchanged /bin/bash (Mount Namespaceを作成)
shell_2# grep /mnt /proc/self/mountinfo 
735 666 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw
736 666 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw

Mount Namespaceを作成するとき親のNamespaceのプロパゲーション設定を変更しないように--propagation unchangedを指定しています。親のNamespaceとmountinfoファイルを比較すると、親のマウントの状態がそのままコピーされていることがわかります(ID:242が同じ)。

ここでこの名前空間内で新たにマウント操作を行ってみましょう。

shell_2# mount -t tmpfs tmpfs /mnt_priv/a
shell_2# mount -t tmpfs tmpfs /mnt_shrd/b
shell_2# grep /mnt /proc/self/mountinfo 
735 666 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw
736 666 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw
737 735 0:56 / /mnt_priv/a rw,relatime - tmpfs tmpfs rw
738 736 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw

共有マウント下の/mnt_shrd/bはsharedとして新たにIDが割り当たっています。いっぽうでprivateの/mnt_priv/aはprivateです。ここで最初のシェルに戻ってみましょう。

shell_1# grep /mnt /proc/self/mountinfo 
325 31 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw
382 31 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw
739 382 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw (2番目のシェルでsharedだったマウント)

2番目のシェルでsharedとなっていたID:222のマウントが共有されています。

ここでさらに最初のシェルでマウント操作を行ってみます。

shell_1# mount -t tmpfs tmpfs mnt_priv/c
shell_1# mount -t tmpfs tmpfs mnt_shrd/d
shell_1# grep /mnt /proc/self/mountinfo 
325 31 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw
382 31 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw
739 382 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw
753 325 0:58 / /mnt_priv/c rw,relatime - tmpfs tmpfs rw
754 382 0:59 / /mnt_shrd/d rw,relatime shared:255 - tmpfs tmpfs rw

新たに行ったマウントで、private以下のマウントはprivate、shared以下のマウントにはあらたにID:255が割り当たったsharedとなっています。

ここで再度2番目のシェルを確認してみると、次のようにID:255のマウントも共有されていることがわかります。

shell_2# grep /mnt /proc/self/mountinfo 
735 666 0:52 / /mnt_priv rw,relatime - tmpfs tmpfs rw
736 666 0:55 / /mnt_shrd rw,relatime shared:242 - tmpfs tmpfs rw
737 735 0:56 / /mnt_priv/a rw,relatime - tmpfs tmpfs rw
738 736 0:57 / /mnt_shrd/b rw,relatime shared:222 - tmpfs tmpfs rw
755 736 0:59 / /mnt_shrd/d rw,relatime shared:255 - tmpfs tmpfs rw (最初のシェルでsharedだったマウント)

このようにsharedであればMount Namespaceをまたがっていてもマウントが共有されます。

マウントプロパゲーション(6)〜 mountinfoファイル(2)〜

これまでの続きです。

これまでと同様に完全に私個人が理解するための資料です。間違いの指摘は大歓迎です。

前回書ききれなかった /proc/[PID]/mountinfo ファイルのエントリーのお話です。

unbindableの場合

unbindableを指定した場合は次のようなエントリになります。

369 31 252:2 /root/test/test/orig /root/test/test/bind rw,relatime unbindable - ext4 /dev/vda2 rw

propagate_fromの場合

propagete_fromはslaveマウントなのですが、slaveとなっているマウントからmasterが見えないときに表示されます。これは、例えばchrootしたあと、masterとなるマウントがroot範囲外となった場合などに起こります。

f:id:defiant:20200703221733p:plain
図1 slaveが連鎖している様子

図1のように、②を③にバインドマウントし、③を④にバインドマウントし、②は③のmaster、③は④のmasterとなっているとします(③のようにmasterかつslaveという設定ができます)。

このようにslaveが連鎖した状態で①の/mntchrootすると、④からmasterである③は見えません。③は②のslaveであり、④から②は見えますので、④のエントリにはpropagate_from:②のように書かれます。実際は②はマウントポイントではないので、IDとしては①のIDが書かれます。

文章にするとわかりづらいですね。実際に試してみましょう(実際に試してもわかりづらいですが…)。

まずは後の操作の準備を行います。ここでは次のような操作を行います。

  1. //mntにバインドマウントする
  2. /proc/mnt/procにバインドマウントする
  3. /mntを一度privateにする。これは/mntはsharedにしたいのですが、他と共有を行わない独立したsharedとするためです
  4. /mntをsharedにする

これで/mntは独自のIDを持つsharedマウントとなります。

# mount --bind / /mnt          ... (1)
# mount --bind /proc /mnt/proc ... (2)
# mount --make-private /mnt    ... (3)
# mount --make-shared /mnt     ... (4)
# cat /proc/self/mountinfo | grep "/mnt"
231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw

ここでmountinfoファイルを確認すると上のようになります。ここで/mntに割り当たっているIDは124となっていますが、これは他とマウントは共有していないマウントです。

次にひとつめのslaveマウントを作成します。図1の②を③にバインドマウントします。

# mkdir /mnt2/etc
# mount --bind /mnt/etc /mnt2/etc
# cat /proc/self/mountinfo | grep "/mnt"
231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
252 25 8:1 /etc /mnt2/etc rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered

これだけでは上のように③(/mnt2/etc)は②(/mnt/etc、つまりは①の/mnt)とマウントを共有するsharedマウントになっていますので、slaveに設定した上で、あとで行うバインドマウントのためにsharedにも設定します。

  1. ③(`/mnt2/etc')をslaveに設定する
  2. ③をsharedに設定する
# mount --make-slave /mnt2/etc  ... (1)
# mount --make-shared /mnt2/etc ... (2)
# cat /proc/self/mountinfo | grep "/mnt"
231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
252 25 8:1 /etc /mnt2/etc rw,relatime shared:131 master:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered

mountinfoを確認すると③(/mnt2/etc)は独自のID(131)を持つshared、かつ①のマウント(②のマウントポイント)を参照するslaveとなっています(masterが①のIDである124)。

さらに次のslaveマウントを行います。

  1. ③(/mnt2/etc)を④(/mnt/tmp/etc)にバインドマウントする
  2. ④(/mnt/tmp/etc)をslaveに設定する
# mkdir /mnt/tmp/etc
# mount --bind /mnt2/etc /mnt/tmp/etc ... (1)
# mount --make-slave /mnt/tmp/etc     ... (2)
# cat /proc/self/mountinfo | grep "/mnt"
231 25 8:1 / /mnt rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
238 231 0:4 / /mnt/proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
252 25 8:1 /etc /mnt2/etc rw,relatime shared:131 master:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
265 231 8:1 /etc /mnt/tmp/etc rw,relatime master:131 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered

mountinfoを確認すると④(/mnt/tmp/etc)は③(/mnt/etc)をmasterとするslaveになっていることがわかります。

これで図1の状態になりましたので、①(/mnt)にchrootしてみましょう。

# chroot /mnt
# cat /proc/self/mountinfo
231 25 8:1 / / rw,relatime shared:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered
238 231 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
265 231 8:1 /etc /tmp/etc rw,relatime master:131 propagate_from:124 - ext4 /dev/sda1 rw,errors=remount-ro,data=ordered

③(/mnt2/etc)が④(/mnt/tmp/etc)から見えなくなりましたので、④のエントリーには②(つまりは①)のIDである124がpropagate_fromとして表示されています。