pivot_root
Rev.7を表示中。最新版はこちら。
chrootはカレントプロセスのルートを変更します。pivot_rootもカレントプロセス下での処理ですが、そのルートファイルシステムその物を差し替えます。言い換えればファイルシステムのツリーを更新することになります。LiveCDはCDがルートファイルシステムとなります。しかしこれだと、システムCDを差し替えてデータCDを読む。と言った操作ができません。この場合、ルートファイルシステムを、CDから別のファイルシステム(ramfs)に切り替えればいいわけです。このようなケースの場合に使われるのが、pivot_rootです。ちなみに下記はchrootの処理です。カレントプロセスのfs->rootに、変更するpathを設定しているだけです。void set_fs_root(struct fs_struct *fs, struct path *path) { struct path old_root; write_lock(&fs->lock); old_root = fs->root; fs->root = *path; path_get(path); write_unlock(&fs->lock); if (old_root.dentry) path_put(&old_root); }と書きながら、ソースを読んでいくとふと、深みにはまってしまいました。上の説明でルートファイルシステムを差し替えると言うのは正しくないように思います・・・?。正しくは(と思われる。)、カレントプロセスのrootの所属するファイルシステムの/に差し替える。と言うのが正しいのではと理解しました。
例えば以下のマウント構成で、あるプロセス下でchrootでroot=/mnt/currentといたします。それをpivot_rootで/mnt/newすると、/mnt/currentがvfsmnt3になるというのではと・・・。
/ <-vfsmnt1 /mnt/current <-vfsmnt2 /mnt/new <-vfsmnt3通常プロセスのrootのファイルシステムは、chrootで別ファイルシステムに変更しない限り、ルートファイルシステムのルートです。従ってpivot_rootは結果的にルートファイルシステムの切り替え手段という事での理解ではと。そのうち検証してみたいと思っています。
new_rootが切り替え先のディレクトリで、put_oldが元のルートファイルシステムとしてアタッチされます。従ってルートファイルシステムを切り替えても、もとのルートファイルシステムはput_oldとして参照する事が可能です。new_rootからパスを辿って下位に向かって検索できるなら、put_oldとnew_rootは同じファイルシステムである必要はありません。なお切り替え元のファイルシステムとnew/oldのファイルシステムが同じではいけません。同じなら切り替える意味がありません。
pivot_rootが成功すると、システムで動いているすべてのプロセスのrootとpwdで、切り替え前のファイルシステムだったものは、新しいファイルシステムとして更新します。
SYSCALL_DEFINE2(pivot_root, const char __user *, new_root, const char __user *, put_old) { struct vfsmount *tmp; struct path new, old, parent_path, root_parent, root; int error; : error = user_path_dir(new_root, &new); error = user_path_dir(put_old, &old); root = current->fs->root; : 切り替えプロセスのrootのファイルシステムが、切り替え先と同じファイルシステムなら エラーです。 newとoldでチェックしているのは、2つは同じファイルシステムである必要がないためです。 if (new.mnt == root.mnt || old.mnt == root.mnt) goto out2; error = -EINVAL; newと違ってrootはプロセス下のdentry情報故、以下のの条件文で、はまってしまいました。 カレントプロセスのrootは、そのファイルシステムのmnt_rootである必要があります。 rootがルートファイルシステムの場合、chrootしてはいけないと言うことです。 ルートファイルシステムでないファイルシステムを切り替える場合、そこにchrootしておく必要があります。 if (root.mnt->mnt_root != root.dentry) goto out2; /* not a mountpoint */ カレントプロセスのrootのファイルシステムの親がいないとは、マウントされてないという事です。 なお、rootf下で直接展開されたシステムもこれに該当します。 if (root.mnt->mnt_parent == root.mnt) goto out2; /* not attached */ 以下の2つも切り替わるファイルシステムについても、同じチェックが行われます。 if (new.mnt->mnt_root != new.dentry) goto out2; /* not a mountpoint */ if (new.mnt->mnt_parent == new.mnt) goto out2; /* not attached */ tmp = old.mnt; spin_lock(&vfsmount_lock); new_root/put_oldについてのチェックです。 if (tmp != new.mnt) { ファイルシステムが異なる場合、old.mnt->mnt_parentを遡ることで、new.mntにたどり着けるかチェックします。 for (;;) { if (tmp->mnt_parent == tmp) goto out3; /* already mounted on put_old */ if (tmp->mnt_parent == new.mnt) break; tmp = tmp->mnt_parent; } if (!is_subdir(tmp->mnt_mountpoint, new.dentry)) goto out3; ファイルシステムが同じなら、サブディレクトリとなっているかをチェックします。 } else if (!is_subdir(old.dentry, new.dentry)) goto out3; 切り替えファイルシステムが、ルートファイルシステムと言う条件はありません。 root/new_rootのファイルシステムをマウントツリーから切り離します。 detach_mnt(new.mnt, &parent_path); detach_mnt(root.mnt, &root_parent); root/new_rootのファイルシステムをold/root_parentとしてマウントツリーに繋ぎます。 attach_mnt(root.mnt, &old); attach_mnt(new.mnt, &root_parent); MNT_SHAREでマウントされたファイルシステム下のプロセスに対して、マウントツリが変更された旨のシグナルを送ります。(たぶん) touch_mnt_namespace(current->nsproxy->mnt_ns); spin_unlock(&vfsmount_lock); rootを参照していた他のプロセスのroot/pwdをnewに更新します。 chroot_fs_refs(&root, &new); : }
static void detach_mnt(struct vfsmount *mnt, struct path *old_path) { old_path->dentry = mnt->mnt_mountpoint; old_path->mnt = mnt->mnt_parent; mnt->mnt_parent = mnt; mnt->mnt_mountpoint = mnt->mnt_root; list_del_init(&mnt->mnt_child); list_del_init(&mnt->mnt_hash); old_path->dentry->d_mounted--; }
static void attach_mnt(struct vfsmount *mnt, struct path *path) { mnt_set_mountpoint(path->mnt, path->dentry, mnt); list_add_tail(&mnt->mnt_hash, mount_hashtable + hash(path->mnt, path->dentry)); list_add_tail(&mnt->mnt_child, &path->mnt->mnt_mounts); }
void mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry, struct vfsmount *child_mnt) { child_mnt->mnt_parent = mntget(mnt); child_mnt->mnt_mountpoint = dget(dentry); dentry->d_mounted++; }各プロセスのroot/pwdが、切り替えられた/と同じdentry時のみ(pivot_rootを実行したシェル下で動作させたプロセス群が考えられます。ルートファイルシステムなら、通常全て?のプロセスが対象となるわけです。)、切り替えたdentryに更新します。これはルートファイルシステムでないファイルシステムを切り替えた場合でも、システムのルートファイルシステムとして動作していたプロセスは、問題なく動作することが可能となります。
static void chroot_fs_refs(struct path *old_root, struct path *new_root) { struct task_struct *g, *p; struct fs_struct *fs; read_lock(&tasklist_lock); do_each_thread(g, p) { task_lock(p); fs = p->fs; if (fs) { atomic_inc(&fs->count); task_unlock(p); if (fs->root.dentry == old_root->dentry && fs->root.mnt == old_root->mnt) set_fs_root(fs, new_root); if (fs->pwd.dentry == old_root->dentry && fs->pwd.mnt == old_root->mnt) set_fs_pwd(fs, new_root); put_fs_struct(fs); } else task_unlock(p); } while_each_thread(g, p); read_unlock(&tasklist_lock); }