pivot_root
Rev.3を表示中。最新版はこちら。
hrootはカレントプロセスのルートを変更します。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は同じファイルシステムである必要はありません。
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; : 切り替えルートファイルシステムが、切り替え先と同じファイルシステムならエラーです。 if (new.mnt == root.mnt || old.mnt == root.mnt) goto out2; error = -EINVAL; カレントプロセスのrootは、そのファイルシステムのmnt_rootである必要があります。 (newと違ってrootはプロセス下の情報です。この事ゆえに深みにはまってしまいました) if (root.mnt->mnt_root != root.dentry) goto out2; /* not a mountpoint */ カレントプロセスのrootのファイルシステムの親はいません。これはマウントされていないという事です。なをrootfs及びそこに直接展開されたシステムもこれに該当します。 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についてのチェックです。ファイルシステムが異なる場合、old.mnt->mnt_parentを 遡ることで、new.mntにたどり着けるかチェックします。ファイルシステムが同じなら、サブディレクトリとなっているかをチェックします。 if (tmp != 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++; }
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); }