マウント名前空間を適用する

高度な Linux マウント機能を実際に生かしたアプリケーションの紹介

ユーザーがシステム管理者の決めた構造に制約されることなく、独自のファイルシステムをセットアップできるようにすれば、ユーザーが自分のファイルシステムのツリーを部分的にエクスポートしたり、他のユーザーがエクスポートしたファイルシステムのツリーを自分のツリーにインポートしたりできるようになります。この記事では、マウント・プロパゲーションという手法を使って、Linux® システム管理者がユーザーによるこのようなエクスポートとインポート操作を可能にする方法を順を追って説明します。

Serge E. Hallyn (sergeh@us.ibm.com), Software Engineer, IBM 

Serge Hallyn は、Linux カーネルおよびセキュリティーを専門とする IBM Linux Technology Center のメンバーです。College of William and Mary のコンピューター・サイエンスで博士号を取得した彼は、これまでに複数のセキュリティー・モジュールを作成して貢献してきました。現在彼が専門に取り組んでいるのは、仮想サーバーの機能、アプリケーションのチェックポイント/再起動、そして POSIX ファイル機能のサポートを追加することです。



Ram Pai (linuxram@us.ibm.com), Software Engineer, IBM

Ram Pai は IBM の世界的 Linux Technology Center のメンバーです。コンピューター・サイエンスの修士号を持つ彼は現在、Linux System x Device Driver チームのリーダーを務めています。彼のオープン・ソースへの貢献は 2001年に始まり、その一部には Linux-HA プロジェクトの Consensus Cluster Membership Layer、および Linux カーネルの Readahead アルゴリズムおよび共有サブツリー機能に生かされた顕著な貢献もあります。



2007年 9月 17日

かつての Linux® のファイルシステムは、かなり単純なツリーでした。当時は、プロセスがそれ自体に対して、ファイルシステム・ツリーのルートがシステムのファイルシステム・ルートのサブディレクトリーであるかのように chroot() を実行できました。また、ツリー内でのノードの位置にかかわらず、あらゆるノードで新規デバイスのファイルシステムをオーバーレイすることも可能でした。

2000年、Al Viro により Linux にバインド・マウントとファイルシステム名前空間が導入されました。

  • バインド・マウントは、任意のファイルやディレクトリーを他の場所からアクセスできるようにします。
  • ファイルシステム名前空間は、異なるプロセスに関連付けられた完全に独立したファイルシステムのツリーです。

clone(2)

Linux の man ページの説明より: clone(2) システム・コールは、(新規の) 子プロセスが呼び出し元のプロセスと実行コンテキストを部分的に共有できるように子プロセスを生成します。共有される部分には、メモリー空間、ファイル・ディスクリプターのテーブル、シグナル・ハンドラーのテーブルなどがあります。詳細は「参考文献」を参照してください。

プロセスが clone(2) で現行ファイルシステム・ツリーのコピーを要求すると (詳細は「参考文献」を参照)、新規プロセスには元のプロセスとまったく同じファイルシステム・ツリーのコピーが作成されます。このコピーが作成された後は、そのツリーのいずれかのコピーでマウント・アクションが行われても、そのアクションが他のコピーに反映されることはありません。

プロセス単位のファイルシステム名前空間は理論上は非常に有効ですが、実際にはファイルシステム名前空間がプロセス単位で完全に分離されていると、あまりにも大きな制約が生じました。プロセスがシステムのファイルシステム名前空間のコピーの複製を行うと、元のファイルシステム名前空間で行われたマウントはユーザーのコピーに反映されなくなるため、すでに実行中のシステム・デーモンはユーザー用に CD-ROM を自動マウントすることができないのです。

こうした状況に対処するために 2006年に導入されたのが、マウント・オブジェクト間の関係を定義するマウント・プロパゲーションです。このマウント・オブジェクト間の関係によって、マウント・オブジェクトで行われたマウント・イベントがシステム内の他のマウント・オブジェクトにどのように伝播されるかが決まります。

  • 2 つのマウント・オブジェクト間に共有 (shared) 関係がある場合、双方のマウント・オブジェクトが相互にマウント・イベントを伝播し合います。
  • 2 つのマウント・オブジェクト間に従属 (slave) 関係がある場合、従属するマウント・オブジェクトだけが他方のマウント・オブジェクトでのマウント・イベントを受け取り、従属するマウント・オブジェクトがもう一方にマウント・イベントを伝播することはありません。

イベントを伝播するマウント・オブジェクトは shared マウントと呼ばれ、マウント・イベントを受け取るマウント・オブジェクトは slave マウントと呼ばれます。マウント・イベントを伝播することも、受け取ることもないマウント・オブジェクトは、private マウントと呼ばれます。もう 1 つ、unbindable マウントと呼ばれる特殊なマウント・オブジェクトもあります。これは private マウントに似たマウント・オブジェクトですが、それ自体をバインド・マウントすることはできません。unbindable マウントは特に、マウント・オブジェクトの急激な増加を抑制するのに役立ちます (これについては、後で詳しく説明します)。

デフォルトでは、すべてのマウントが private として設定されます。マウント・オブジェクトを shared マウントにするには、以下のコマンドで明示的に shared のマークを付けます。

mount --make-shared <mount-object>

例えば、/ に位置するマウントを shared にするには、以下のコマンドを実行します。

mount --make-shared /

shared マウントから複製されるマウント・オブジェクトも shared マウントになるため、この 2 つのマウント・オブジェクトは互いに伝播し合います。

マウント・オブジェクトに slave のマークを付けるには、以下のコマンドを実行して、shared マウントを slave マウントに明示的に変換します。

mount --make-slave <shared-mount-object>

slave マウントから複製されるマウント・オブジェクトも同じく slave マウントになります。そのマスターは、元の slave マウントのマスターと同じです。

マウント・オブジェクトに private のマークを付けるには、以下のコマンドを実行します。

mount --make-private <mount-object>

マウント・オブジェクトに unbindable のマークを付けるには、以下のコマンドを実行します。

mount --make-unbindable <mount-object>

最後に付け加えると、これらのコマンドはいずれも再帰的に適用することができます。つまり、ターゲット・マウントの配下のすべてのマウントにコマンドが適用されます。

以下はその一例です。

mount --make-rshared /

上記のコマンドは、/ の配下にあるすべてのマウントを shared マウントに変換します。

ログイン別名前空間

リスト 1 は PAM (Pluggable Authentication Module) からの抜粋で、ここでは root 以外のすべてのユーザーを専用の名前空間に配置しています。/tmp/priv/USER ディレクトリーが存在する場合、このディレクトリーがユーザーの専用の名前空間内の /tmp にバインド・マウントされます。

リスト 1. ログイン別名前空間に対応した PAM からの抜粋
#define DIRNAMSZ 200
int handle_login(const char *user)
{
        int ret = 0;
        struct stat statbuf;
        char dirnam[DIRNAMSZ];

        if (strcmp(user, "root") == 0)
                return PAM_SUCCESS;

        ret = unshare(CLONE_NEWNS);
        if (ret) {
                mysyslog(LOG_ERR, "failed to unshare mounts for %s\n", user);
                return PAM_SESSION_ERR;
        }

        snprintf(dirnam, DIRNAMSZ, "/tmp/priv/%s", user);
        ret = stat(dirnam, &statbuf);
        if (ret == 0 && S_ISDIR(statbuf.st_mode)) {
                ret = mount(dirnam, "/tmp", "none", MS_BIND, NULL);
                if (ret) {
                        mysyslog(LOG_ERR, "failed to mount tmp for %s\n", user);
                        return PAM_SESSION_ERR;
                }
        } else
                mysyslog(LOG_INFO, "No private /tmp for user %s\n", user);
        return PAM_SUCCESS;
}

int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
                const char **argv)
{
        const char *PAM_user = NULL;
        char *fnam;
        int ret;

        ret = pam_get_user(pamh, &PAM_user, NULL);
        if (ret != PAM_SUCCESS) {
                mysyslog(LOG_ERR, "PAM-NS: couldn't get user\n");
                return PAM_SESSION_ERR;
        }

        return handle_login(PAM_user);
}

この PAM モジュールを利用するには、まず、「ダウンロード」セクションから完全な pam_ns.c ファイルとこれに対応する makefile の一式をダウンロードしてコンパイルします。その結果作成された pam_ns.so ファイルを /lib/security/ にコピーした後、以下のエントリーを追加します。

session   required   pam_ns.so

上記のエントリーは、/etc/pam.d/login および /etc/pam.d/sshd に追加します。それが終わったら、ユーザー USER の専用の tmp を作成します。

mkdir /tmp/priv
chmod 000 /tmp/priv
mkdir /tmp/priv/USER
chown -R USER /tmp/priv/USER

ここで、一方の端末では root としてログインし、もう一方の端末では USER としてログインします。USER として以下を実行してください。

touch /tmp/ab
ls /tmp

USER の /tmp には、新しく作成されたファイルだけが含まれることに注目してください。

次に、root 端末で /tmp に含まれるコンテンツのリストを取得します。このディレクトリーには、他のファイルは含まれていても、/tmp/ab は含まれていません。これは、/tmp ディレクトリーはまさに独立したディレクトリーだからです。root 端末から USER の /tmp を検索するには以下を入力します。

ls /tmp/priv/USER

ab ファイルがあることを確認したら、今度は root 端末で何らかのものを /mnt にマウントします。

mount --bind /dev /mnt

mount(8) および unshare(2)

Linux man ページの説明より: mount(8) コマンドは、デバイスで検出されたファイルシステムを大規模なファイル・ツリーに接続する機能があります。一方、unshare(2) システム・コールを使用すると、呼び出し元のプロセスで、選択したリソースの名前空間を元の名前空間のコピーに置き換えることができます。「参考文献」を参照してください。

これによって、/dev の中身は root 端末の /mnt には表示されますが、USER 端末には表示されません。つまり、この 2 つの端末のマウント・ツリーは完全に切り離されているということです。マウント・プロパゲーションに関するディレクティブを指定するには、mount(8) コマンドを使用します。デフォルトではすべてのマウントが private なので、USER がログインする前に例えば以下のコマンドを実行することができます。

mount --make-rshared /

このコマンドにより、以降の非共有の名前空間の間でマウント・イベントが伝播されるようになりますが、USER がログインした後は /tmp への /tmp/priv/USER のマウントは親の名前空間に伝播されません。これを解決するには、pam_ns.so のファイルシステムに slave のマークを付けます (リスト 2 を参照)。

リスト 2. ユーザーの名前空間に slave のマークを付ける PAM モジュール
#define DIRNAMSZ 200
#ifndef MS_SLAVE
#define MS_SLAVE 1<<19
#endif
#ifndef MS_REC
#define MS_REC 0x4000
#endif
int handle_login(const char *user)
{
        int ret = 0;
        struct stat statbuf;
        char dirnam[DIRNAMSZ];

        if (strcmp(user, "root") == 0)
                return PAM_SUCCESS;

        ret = unshare(CLONE_NEWNS);
        if (ret) {
                mysyslog(LOG_ERR, "failed to unshare mounts for %s\n", user);
                return PAM_SESSION_ERR;
        }

        ret = mount("", "/", "dontcare", MS_REC|MS_SLAVE, ""));
        if (ret) {
                mysyslog(LOG_ERR, "failed to mark / rslave for %s\n", user);
                return PAM_SESSION_ERR;
        }

        snprintf(dirnam, DIRNAMSZ, "/tmp/priv/%s", user);
        ret = stat(dirnam, &statbuf);
        if (ret == 0 && S_ISDIR(statbuf.st_mode)) {
                ret = mount(dirnam, "/tmp", "none", MS_BIND, NULL);
                if (ret) {
                        mysyslog(LOG_ERR, "failed to mount tmp for %s\n", user);
                        return PAM_SESSION_ERR;
                }
        } else
                mysyslog(LOG_INFO, "No private /tmp for user %s\n", user);
        return PAM_SUCCESS;
}

ユーザー別ルート

LSPP

Common Criteria の LSPP (Labeled Security Protection Profile) では、IT 製品の一連のセキュリティー機能要件と保証要件を指定しています。LSPP は 2 種類のアクセス制御機構をサポートします。一方は、個々のユーザーが自分で制御するリソースをどのように共有するかを指定できるアクセス制御機構です (セキュリティー・マークまたは「ラベル」を使用)。そしてもう一方では、ユーザー間での共有に制限を設けます。LSPP が提供する保護レベルは、「不注意あるいは偶然によるシステム・セキュリティー侵害の脅威に対する保護を必要とする、敵意のない十分に管理されたユーザー・コミュニティー」に適切であると見なされています。「参考文献」を参照してください。

「ログイン別名前空間」のセクションでは、ユーザーに専用の名前空間を提供するマウント名前空間の単純な使い方について説明しました。マウント・プロパゲーションを使用すると、ユーザーに /tmp ディレクトリーを提供するには申し分のないソリューションとなります。さらに pam_ns.c で解析される構成ファイルを追加すれば、ユーザーごとに追加ディレクトリーを指定することもできます。LSPP システムではこの方法を使って /home/USER にログイン・プロセスの結果に応じたディレクトリーをマウントし、さまざまにインスタンス化したホーム・ディレクトリーを提供します。

ただし、同じユーザーがログインするごとに専用の slave ファイルシステムを受け取ることになるため、ユーザーがあるログイン・セッションで行ったマウントは、別のログイン・セッションでは反映されません。

管理者以外のユーザーがファイルシステムをマウントする方法はいくつかあります。例えば FUSE を使用すれば、ユーザーが sshfs (SSH ファイルシステム) やループバック・ファイルシステムをマウントすることが可能になります (「参考文献」を参照)。このようにして行ったマウントを他のユーザーと共有するという問題はともかくとして (これについては後で説明)、そのマウントが該当するユーザーがログインしている 1 つの端末には反映されても、他の端末には反映されなければ混乱を免れません。「ログイン別名前空間」のセクションで説明した手法を使えば、まさにこのようなことが起こります。

リスト 3 は、pam_chroot.so pam モジュールからの該当する部分の抜粋です。pam_ns.so ではログイン時にマウント名前空間を複製しますが、pam_chroot.so モジュールではユーザーごとのファイルシステムが /share/USER/root の下に設定されるという前提で、単純に chroot() を使ってユーザーをユーザ専用のファイルシステムにロックします。

リスト 3. chroot() を使用した PAM モジュール
int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
                const char **argv)
{
        const char *PAM_user = NULL;
        char fnam[400];
        int ret, err, count, i;
        struct mount_entries *entries;
        struct stat statbuf;

        ret = pam_get_user(pamh, &PAM_user, NULL);
        if (ret != PAM_SUCCESS) {
                mysyslog(LOG_ERR, "PAM-MOUNT: couldn't get user\n");
                return PAM_SESSION_ERR;
        }

        /* check whether /share/$pam_user/root exists.  If so, chroot to it */
        sprintf(fnam, "/share/%s/root", PAM_user);
        ret = stat(fnam, &statbuf);
        if (ret == 0 && S_ISDIR(statbuf.st_mode)) {
                ret = chroot(fnam);
                if (ret) {
                        mysyslog(LOG_ERR, "PAM-MOUNT: unable to chroot to %s\n", fnam);
                        return PAM_SESSION_ERR;
                }
        }

        return PAM_SUCCESS;
}

この場合、すべてのマウントはシステムの起動時に前もって行われます。以下は、ブート後に実行するコマンド例です。

mkdir -p /share/USER/root
mount --make-rshared /
mount --rbind / /share/USER/root
mount --make-rslave /share/USER/root
mount --bind /share/USER/root/tmp/priv/USER /share/USER/root/tmp

上記では、専用の名前空間は使用されていません。代わりに USER のそれぞれのログインに対して、同じディレクトリー /share/USER/root の下で chroot() が実行されます。したがって、USER のどのログインで行われたマウントであっても、そのマウントはすべてのログインで表示されます。一方、OTHERUSER の場合は、/share/OTHERUSER/root の下で chroot() が実行されるため、USER のマウント・アクティビティーは表示されません。

この手法の欠点は、何らかの特権は必要になるものの、通常の chroot() から抜け出すことができるということです。一例として、CAP_SYS_CHROOT などの特定の特権で実行すると、chroot() から抜け出すプログラムのソース (「参考文献」を参照) によって、プログラムは実際のファイルシステムのルートへ抜け出ることになります。このような事態は、ユーザー別にファイルシステム・ツリーを使用する実際の動機および使用方法によっては、問題になる可能性があります。

pivot_root(2) および chroot(2)

Linux man ページの説明より: pivot_root(2) コマンドは現行プロセスのルート・ファイルシステムを put_old ディレクトリーに移動し、new_root を現行プロセスの新しいルート・ファイルシステムに設定します。chroot(2) コマンドは、ルート・ディレクトリーをパスに指定されたディレクトリーに変更します。変更後のルート・ディレクトリーは、現行プロセスのすべての子プロセスに継承されます。ルート・ディレクトリーを呼び出せるのは、特権プロセスのみです。「参考文献」を参照してください。

この問題に対処する方法は、chroot(2) の代わりに専用の名前空間で pivot_root(2) を使用し、ログインのルートを /share/USER/root に変更することです。chroot() はプロセスのファイルシステムのルートを指定された新規ディレクトリーに変更するだけですが、pivot_root() は指定された new_root ディレクトリーをマウント・ポイントから切り離し、プロセスのルート・ディレクトリーに接続します。マウント・ツリーには新規ルートの親がないため、chroot() での場合のようにシステムがだまされてルート・ディレクトリーに入り込むことがありません。以降の例では、この pivot_root() による手法を使用します。


ユーザー別ルートに対応したシステムのセットアップ

これまでは、ログイン時に必要な操作を含め、ユーザーごとに専用のマウント・ツリーを実装する方法を詳しく見てきました。このセクションではユーザー・アカウントの作成時とシステムのブートアップ時に使用する、より完全なスクリプトを検討します。

リスト 4 は、ユーザーの作成時に実行されるスクリプトです。

リスト 4. ユーザー作成用のスクリプト
create_user_tree {
        user = $1
        mkdir /user/$user
        mount --rbind / /user/$user
        mount --make-rslave /user/$user
        mount --make-rshared /user/$user

	#create a private mount. This is to facilitate pivot_root
	#to temporarily place the old root here before detaching the
	#entire old root tree. NOTE: pivot_root will not allow old root
	#to be placed under a shared mount.
	pushd /user/$user/
	mkdir -p __my_private_mnt__
	mount --bind __my_private_mnt__ __my_private_mnt__
	mount --make-private __my_private_mnt__
	popd
}

このスクリプトは、init_per_user_namespaceスクリプト (この後で説明) がすでに実行済みであることを前提とします。/user/ の下にアカウント用のディレクトリーが作成された後、ルート・ディレクトリーが再帰的に /user/$user/ の下にバインド・マウントされます。この再帰的にコピーされるルート・ファイルシステムのツリーは、ユーザーによるマウント・アクティビティーの永続ストア (リブートではなく、ログインするごとに変わることのないストア) となります。

コピーされたツリーはルート・ツリーの slave として設定されるため、ルート・ツリーでのマウント・アクションはこのコピーに伝播されますが、その逆の伝播は行われません。また、このツリーには shared のマークも付けられるため、以降のコピー (つまり、名前空間の複製によって作成されたコピー) はマウントのピアとなります。したがって、いずれかのコピーで行われるマウント・アクションはその他すべてのコピーに伝播されます。

最後に、__my_private_mnt__ という private マウントが作成されます。このマウントを作成する理由は、pivot_root() (リスト 6 に記載) を実行できるようにして、ツリーを削除する前に一時的にルート・マウントを行うためです。このステップに関しては、あまり深く考えないでください。このステップを行う根拠は、この後説明する pivot_root() の動作から明らかになります。ここでは、pivot マウントはマウントのタイプが shared の場合には成功しないということだけ覚えておいてください。

リスト 5 に、システムのブート時に実行されるスクリプトを記載します。

リスト 5. ブート時にシステムを初期化するスクリプト
init_per_user_namespace {

	#start with a clean state by marking
	#all mounts as private.
	mount --make-rprivate /

        #create a unbindable mount called 'user'
        #and have all the users to bind the entire
        #system tree '/' under them.

        mkdir /user
        mount --bind /user /user
        mount --make-rshared /
        mount --make-unbindable /user
        foreach user in existing_user {
                create_user_tree $user
        }
}

このスクリプトは、ユーザーごとのマウント・ツリーを保持する場所となる /user ディレクトリーを作成し、続いて /user 自体のバインド・マウントを行います。マウント・ポイントに指定できるのは、--rshared などのマウント・プロパゲーション・ディレクティブのみです。このステップにより、共有マウント・ポイントが /user に存在することを確実にしています。

次に、ファイルシステムのルートに --rshared のマークが付けられます。これは、以降のコピー (バインド・マウントあるいはマウント名前空間の複製によって作成) がこのマウントのピアとなり、いずれかのツリーで行われたマウント・アクションがすべてのピアにコピーされるようにするためです。

さらに、/user に位置するマウントには unbindable のマークが付けられます。マウント・ツリー全体はユーザーごとに 1 度、再帰的にコピーされます。そのため通常は、最初のユーザーのコピーが /user/$user_1 に作成されると、/user/$user_2 に作成されるコピーには、/user/$user_2/user/$user_1 の下に /user/$user_1 の再帰的なコピーが含まれることになります。そうなるとご想像のとおり、たちまち大量のメモリーが消費されてしまいます。そこで、/user に unbindable のマークを付けることで、/ が再帰的にバインド・マウントされるときに /user がコピーされないようにしているというわけです。

最後に、リスト 4 のスクリプトが各ユーザーに対して 1 度実行されます。このスクリプトは、/user/$user ディレクトリーが存在しない場合にはこのディレクトリーを作成し、前述の正しいマウント・プロパゲーションをセットアップします。

リスト 6 は、ユーザーがログインするたびに実行される PAM モジュールからの抜粋です。

リスト 6. ユーザー・ログイン時の PAM コードの抜粋
#ifndef MNT_DETACH
#define MNT_DETACH		0x0000002
#endif
#ifndef MS_REC
#define MS_REC			0x4000
#endif
#ifndef MS_PRIVATE
#define MS_PRIVATE              1<<18   /* Private */
#endif

#define DIRNAMSZ 200
int handle_login(const char *user)
{
	int ret = 0;
	struct stat statbuf;
	char dirnam[DIRNAMSZ], oldroot[DIRNAMSZ];

	snprintf(dirnam, DIRNAMSZ, "/user/%s", user);
	ret = stat(dirnam, &statbuf);
	if (ret != 0 || !S_ISDIR(statbuf.st_mode))
		return PAM_SUCCESS;

	ret = unshare(CLONE_NEWNS);
	if (ret) {
		mysyslog(LOG_ERR, "failed to unshare mounts for %s, error %d\n",
			user, errno);
		return PAM_SESSION_ERR;
	}

	ret = chdir(dirnam);
	if (ret) {
		mysyslog(LOG_ERR, "failed to unshare mounts for %s, error %d\n",
			user, errno);
		return PAM_SESSION_ERR;
	}

	snprintf(oldroot, DIRNAMSZ, "%s/__my_private_mnt__", dirnam);
	ret = pivot_root(dirnam, oldroot);
	if (ret) {
		mysyslog(LOG_ERR, "failed to pivot_root for %s, error %d\n",
			user, errno);
		mysyslog(LOG_ERR, "pivot_root was (%s,%s)\n", dirnam, oldroot);
		return PAM_SESSION_ERR;
	}
	
	ret = mount("", "/__my_private_mnt__", "dontcare", MS_REC|MS_PRIVATE, "");
	if (ret) {
		mysyslog(LOG_ERR, "failed to mark /tmp private for %s, error %d\n",
			user, errno);
		return PAM_SESSION_ERR;
	}

	ret = umount2("/__my_private_mnt__", MNT_DETACH);
	if (ret) {
		mysyslog(LOG_ERR, "failed to umount old_root %s, error %d\n",
			user, ret);
		return PAM_SESSION_ERR;
	}

	return PAM_SUCCESS;
}

このモジュールはまず、ログインを行っているユーザーを対象とした /user/USER ツリーの有無をチェックします。存在しない場合は単にユーザーをログインさせ、それ以降のアクションは一切行いません。

/user/USER ツリーが存在する場合には、最初のステップとして、このログイン・プロセスでのタスクを対象とした専用の名前空間を複製します。つまり、それぞれのログイン・プロセスに固有のシステム初期マウント・ツリーのコピーが作成されるということですが、これらのツリーは分離されているわけではありません。コピーされたツリー内の各マウント・ノードは、初期ツリーの対応するマウント・ノードと共有されます。

ログイン・プロセスは次に pivot_root() を使用して、ファイルシステムのルートを /user/$user に変更します。元のルートは新規 __my_private_mnt__ にマウントされたままの状態となります。

次のステップでは __my_private_mnt__ に private のマークを付け、元のルート・マウント・ツリー、そしてルート・マウント・ツリーのコピーに以降のアンマウントが伝播されないようにします。

その上で、元のルートが __my_private_mnt__ からアンマウントされます。

ユーザー作成用のスクリプト (リスト 4 を参照) では、__my_private_mnt__ ディレクトリーを private マウントに指定し、これによって pivot_root() を実行できるようにすると説明しました。その根拠となっているのは、十分に文書化されてはいませんが、古いルートと新しいルートのマウント・プロパゲーション・ステータスに関する pivot_root() の制約です。pivot_root() が正常に機能するためには、以下のマウントが共有オブジェクトであってはなりません。

  1. 古いルートのターゲット・ロケーション
  2. 新規ルートの現行の親 (pivot_root() を呼び出した時点での親)
  3. 新規ルートがターゲットとする親

上記の最初の条件には、リスト 4 の最後のほうで __my_private_mount を private にすることで対処できます。2 番目の条件は、新規ルートの現行の親は /user であり、/user にあるマウントは unbindable マウントであることから、すでに満たされています。3 番目の条件についてもすでに満たされており、新規ルートがターゲットとする親は現行ルートの親にもなっています。これに該当するマウントは、表示には現れない rootfs マウントで、これはすでに private となっています。

このセクションではユーザーごとにマウント・ツリーを実装する方法を説明しましたが、この実装の場合、マウント・イベントは該当するユーザーのログイン・セッションのすべてで共有されますが、他のユーザーには表示されません。次のセクションでは、ユーザー同士でマウント・ツリーを共有できるようにする方法を説明します。


選択的に共有するユーザー別マウント・ツリー

ユーザーごとにマウント・ツリーを提供する方法について説明し終わったので、今度はマウント・ツリーを部分的にユーザー共有として指定できるようにする方法を説明します。以下は、システム・ブート用のスクリプトです。

リスト 7. システム・ブート用のスクリプト
init_per_user_namespace {
	mkdir -p /user/slave_tree
	mkdir -p /user/share_tree

	#start with a clean state. Set all mounts to private. 
	mount --make-rprivate /

	mount --bind /user /user
	mount --bind /user/share_tree /user/share_tree
	mount --bind /user/slave_tree /user/slave_tree

	mount --make-rshared  /

	mount --make-unbindable /user

	for user in `cat /etc/user_list`; do
		sh /bin/create_user_tree $user
	done
}

ここでまず作成しているのは、各ユーザーのルート・ディレクトリーを保持する /user マウントです。/user の下には /user/share_tree という別のディレクトリーも作成しています。このディレクトリーには、それぞれのユーザーが他のユーザーと共有しても構わないマウントが保持されます。さらに /user/slave_tree ディレクトリーを作成し、ここに、他のユーザーによる変更を許可せずに共有するマウントを保持するようにします。マウントが無制限に作成されないようにするため、/user に位置するマウントには当然、unbindable のマークを付けます。そして最後に、create_user_tree を呼び出して各ユーザーのマウント・ツリーを作成します。

リスト 8 に、マウント・ツリーを作成し、他のユーザーとマウントを共有できるようにするために必要なステップを説明します。

リスト 8. ユーザー作成用のスクリプト
create_user_tree {

        user = $1
	mkdir -p /user/$user

	#copy over the entire mount tree under /user/$user
	mount --rbind / /user/$user
	make --make-rslave /user/$user
	make --make-rshared /user/$user

	cd /user/$user/home/$user

	#export my shared exports
	mkdir -p my_shared_exports
	chown $user my_shared_exports
	mount --bind my_shared_exports my_shared_exports
	mount --make-private my_shared_exports
	mount --make-shared my_shared_exports
	mkdir -p /user/share_tree/$user
	mount --bind my_shared_exports /user/share_tree/$user

	#export my slave exports
	mkdir -p my_slave_exports
	chown $user my_slave_exports
	mount --bind my_slave_exports my_slave_exports
	mount --make-private my_slave_exports
	mount --make-shared my_slave_exports
	mkdir -p /user/slave_tree/$user
	mount --bind my_slave_exports /user/slave_tree/$user

	cd /user/$user

	#import everybody's shared exports
	mkdir -p others_shared_exports
	mount --rbind /user/share_tree others_shared_exports

	#import everybody's slave exports
	mkdir -p others_slave_exports
	mount --rbind /user/slave_tree others_slave_exports
	mount --make-rslave others_slave_exports

	#setup a private mount in the user's tree, This is to facilitate
	# pivot_mount executed later, during new user-logins.
	mkdir -p __my_private_mnt__
	mount --bind __my_private_mnt__ __my_private_mnt__
	mount --make-private __my_private_mnt__
}

まず始めに、マウント・ツリー全体を /user/$user に複製します。ユーザーのツリー内には、my_shared_exports という名前の shared マウントを作成します。このマウントをすべてのユーザーにエクスポートする手段として、/user/share_tree/$user の下にマウントを複製します。同様に、ユーザーのツリー内に my_slave_exports を作成し、/user/slave_tree/$user の下に複製することによって、すべてのユーザーにエクスポートします。ここで重要となる考えは、ユーザーが my_shared_tree に何かをマウントするという選択をした場合、そのマウントは自動的に他のすべてのユーザーと共有されるということです。

次に、他のすべてのユーザーの shared マウントをインポートするため、/user/share_tree の下にあるマウント・ツリーを複製し、ログイン・ユーザーの others_shared_exports にマウントします。同じく /user/slave_tree の下にあるマウント・ツリーを複製して others_slave_exports にマウントし、他のすべてのユーザーの slave マウントをインポートします。当然のことながら、これらのマウントはエクスポーターによって slave としてエクスポートされることが意図されているので、マウントを slave に変換します。

共有のための正しい設定をして初期セットアップを完了すると、ユーザー・ログイン・アルゴリズムはリスト 6 と同一になります。ログインしたユーザーには、他のログイン時とまったく同じマウント・ツリーが表示されるだけでなく、他のすべてのユーザーからエクスポートされたすべての shared および slave マウントもそれぞれ /others_shared_export、/others_slave_exports の下に表示されます。

ユーザーが他のユーザーに何かをエクスポートするために必ず必要なことは、my_shared_exports の下にそのコンテンツをマウントするだけです。すると、まるで魔法のように該当するコンテンツが他のすべてのユーザーに表示されます。


まとめ

バインド・マウントは任意のファイルまたはディレクトリー同士の結合を容易にし、名前空間はプロセスをその親固有のマウント・ツリーのコピーと併せて複製できるようにします。さらにマウント・プロパゲーションによって、ファイルシステム・ツリーのコピーが他のコピーに対してマウント・イベントを伝播することも、あるいはコピー同士でマウント・イベントを共有することも可能になります。これらの機能は、ユーザーに独自の準専用のマウント・ツリーを提供するだけでなく、ユーザーが CD-ROM マウントなどといったシステム全体のマウント・イベントを把握したり、ユーザー固有のマウント・イベントを他のユーザーと選択的に共有することを可能にします。

言い換えると、この記事で説明したマウント・プロパゲーションの手法により、ユーザーは別個のファイルシステムをセットアップし、専用のファイルシステムのツリーにさまざまなファイルシステムのツリーを部分的にインポート、エクスポートできるようになるということです。


ダウンロード

内容ファイル名サイズ
Sample mount propagation code for this articledw.mountscode.tgz3KB

参考文献

学ぶために

  • Create uniform namespace using autofs with NFS Version 3 clients and servers」(developerWorks、2007年1月) では、autofs と LDAP のオープン・ソース実装を使用して、複数のファイル・サーバーからエクスポートしたデータに同じグローバル・マウント・ポイントでアクセスする方法を説明しています。
  • System Administration Toolkit: Migrating and moving UNIX filesystems」(developerWorks、2006年7月) では、ライブ・システムでファイルシステム全体を転送する方法を、作成、コピー、そして再有効化の手順に沿って説明しています。
  • Using ReiserFS with Linux」(developerWorks、2006年4月) は、「冒険好きな人たちのためのもう 1 つの高度なファイルシステム」を取り上げた記事です。
  • Differentiating UNIX and Linux」(developerWorks、2006年3月) では、Linux と UNIX でのファイルシステム・サポートの違いを分かりやすく解説しています。
  • Linux マニュアル・ページで、clone(2)unshare(2)mount(8)pivot_root(2)chroot(2) についての詳細を学んでください。
  • Common Criteria の LSPP (Labeled Security Protection Profile) では、IT 製品の一連のセキュリティー機能と保証要件 (2 種類のアクセス制御機構) を指定しています。
  • FUSE (Filesystem in Userspace) では、ユーザー・スペース・プログラムで完全に機能するファイルシステムを実装できます。単純な API、カーネルのパッチや再コンパイルの不要、セキュアな実装、実証された安定性という特徴を備え、特権なしのユーザーが使用できるこのファイルシステムは、2.4.x および 2.6.x カーネルで動作します。
  • sshfs は SSH ファイル転送プロトコルをベースとしたファイルシステム・クライアントです。ほとんどの SSH サーバーにはこのプロトコルのサポートが備わっているため、sshfs をセットアップするためにサーバー側で必要なことは何もありません。クライアント側で ssh を使えばサーバーにログインできます。
  • ループバック・ファイルシステムでは、代替パス名を使って既存のファイルにアクセスできる、新しい仮想ファイルシステムを作成できます。このファイルシステムをいったん作成すれば、元のファイルシステムに影響を与えることなく、その内部に他のファイルシステムをマウントできます。
  • chroot() jail から脱出する方法についての説明を読んでください。
  • 柔軟性の高いユーザー認証機構、Linux PAM により、開発者は認証方式に依存しないプログラムを作成できます (つまり、「デバイスの新規作成」がすべての認証サポート・プログラムを記録することであるとは限りません)。
  • The Linux Documentation Project には、HOWTO 文書をはじめ、各種の有益な文書が豊富に揃っています。
  • developerWorks Linux ゾーンに豊富に揃った Linux 開発者向けの資料を調べてください。記事とチュートリアルの人気ランキングも要チェックです。
  • developerWorks に掲載されているすべての Linux のヒントLinux チュートリアルを参照してください。
  • developerWorks technical events and webcasts で最新情報を入手してください。

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=265747
ArticleTitle=マウント名前空間を適用する
publish-date=09172007