BINDオプションでのmount
mount --bindは、シンボリック/ハードリンクと同じで、リンク元をリンク先から参照するものです。シンボリック/ハードリンクはリンク先inodeで実装され、mount --bindは、bind元ディレクトリをmnt.mnt_rootとするバーチャルなデバイスファイルとし、そのファイルシステムをbind先ディレクトリにmountする事で実装します。
dev_nameが接続元ディレクトリ、dir_nameが接続先ディレクトリです。
(本関数は他から使用されておらず、第二引数のold_nameは、dev_nameとする方が、実装的に適合しているかと思います。)
/ets/fstabでのbindオプションで、そのbind元ディレクトリがmount済みである必要があり、デバイスのあるディレクトリ下の構成のみをbind mountして使用する。と言った事はできません。
dev_nameが接続元ディレクトリ、dir_nameが接続先ディレクトリです。
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data) { int ret; char *kernel_type; char *kernel_dir; char *kernel_dev; unsigned long data_page; ret = copy_mount_string(type, &kernel_type); if (ret < 0) goto out_type; kernel_dir = getname(dir_name); if (IS_ERR(kernel_dir)) { ret = PTR_ERR(kernel_dir); goto out_dir; } ret = copy_mount_string(dev_name, &kernel_dev); if (ret < 0) goto out_dev; ret = copy_mount_options(data, &data_page); if (ret < 0) goto out_data; ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags, (void *) data_page); free_page(data_page); out_data: kfree(kernel_dev); out_dev: putname(kernel_dir); out_dir: kfree(kernel_type); out_type: return ret; }mountコマンドの--bindは、flagsがMS_BINDで、--rbindはMS_BIND | MS_RECで、--bindはdo_loopback(&path, dev_name, FALSE);--rbindはdo_loopback(&path, dev_name, TRUE)コールされます。
long do_mount(char *dev_name, char *dir_name, char *type_page, unsigned long flags, void *data_page) { struct path path; int retval = 0; int mnt_flags = 0; if ((flags & MS_MGC_MSK) == MS_MGC_VAL) flags &= ~MS_MGC_MSK; if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE)) return -EINVAL; if (data_page) ((char *)data_page)[PAGE_SIZE - 1] = 0; retval = kern_path(dir_name, LOOKUP_FOLLOW, &path); if (retval) return retval; retval = security_sb_mount(dev_name, &path, type_page, flags, data_page); if (retval) goto dput_out; if (!(flags & MS_NOATIME)) mnt_flags |= MNT_RELATIME; if (flags & MS_NOSUID) mnt_flags |= MNT_NOSUID; if (flags & MS_NODEV) mnt_flags |= MNT_NODEV; if (flags & MS_NOEXEC) mnt_flags |= MNT_NOEXEC; if (flags & MS_NOATIME) mnt_flags |= MNT_NOATIME; if (flags & MS_NODIRATIME) mnt_flags |= MNT_NODIRATIME; if (flags & MS_STRICTATIME) mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME); if (flags & MS_RDONLY) mnt_flags |= MNT_READONLY; flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT | MS_STRICTATIME); if (flags & MS_REMOUNT) retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags, data_page); else if (flags & MS_BIND) retval = do_loopback(&path, dev_name, flags & MS_REC); else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)) retval = do_change_type(&path, flags); else if (flags & MS_MOVE) retval = do_move_mount(&path, dev_name); else retval = do_new_mount(&path, type_page, flags, mnt_flags, dev_name, data_page); dput_out: path_put(&path); return retval; }clone_mnt()はold_name(bind元)をmnt_rootとするstruct mount *mntを作成し、graft_tree()でそのmntをbind先pathにmountします。copy_tree()はrbindで配下にmountされてるファイルシステムもbindされます。
(本関数は他から使用されておらず、第二引数のold_nameは、dev_nameとする方が、実装的に適合しているかと思います。)
static int do_loopback(struct path *path, char *old_name, int recurse) { LIST_HEAD(umount_list); struct path old_path; struct mount *mnt = NULL, *old; int err = mount_is_safe(path); if (err) return err; if (!old_name || !*old_name) return -EINVAL; err = kern_path(old_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); if (err) return err; err = lock_mount(path); if (err) goto out; old = real_mount(old_path.mnt); err = -EINVAL; if (IS_MNT_UNBINDABLE(old)) goto out2; if (!check_mnt(real_mount(path->mnt)) || !check_mnt(old)) goto out2; err = -ENOMEM; if (recurse) mnt = copy_tree(old, old_path.dentry, 0); else mnt = clone_mnt(old, old_path.dentry, 0); if (!mnt) goto out2; err = graft_tree(mnt, path); if (err) { br_write_lock(vfsmount_lock); umount_tree(mnt, 0, &umount_list); br_write_unlock(vfsmount_lock); } out2: unlock_mount(path); release_mounts(&umount_list); out: path_put(&old_path); return err; }old[bind先]を雛形とするmntにmnt->mnt.mnt_root = dget(root)[bind元]のバーチャルファイルシステムを作成します。通常デバイスだとファイルシステム依存のデバイスのroot inodeからdentryを作成されmnt->mnt.mnt_rootに設定されます。
static struct mount *clone_mnt(struct mount *old, struct dentry *root, int flag) { struct super_block *sb = old->mnt.mnt_sb; struct mount *mnt = alloc_vfsmnt(old->mnt_devname); if (mnt) { if (flag & (CL_SLAVE | CL_PRIVATE)) mnt->mnt_group_id = 0; /* not a peer of original */ else mnt->mnt_group_id = old->mnt_group_id; if ((flag & CL_MAKE_SHARED) && !mnt->mnt_group_id) { int err = mnt_alloc_group_id(mnt); if (err) goto out_free; } mnt->mnt.mnt_flags = old->mnt.mnt_flags & ~MNT_WRITE_HOLD; atomic_inc(&sb->s_active); mnt->mnt.mnt_sb = sb; mnt->mnt.mnt_root = dget(root); mnt->mnt_mountpoint = mnt->mnt.mnt_root; mnt->mnt_parent = mnt; br_write_lock(vfsmount_lock); list_add_tail(&mnt->mnt_instance, &sb->s_mounts); br_write_unlock(vfsmount_lock); if (flag & CL_SLAVE) { list_add(&mnt->mnt_slave, &old->mnt_slave_list); mnt->mnt_master = old; CLEAR_MNT_SHARED(mnt); } else if (!(flag & CL_PRIVATE)) { if ((flag & CL_MAKE_SHARED) || IS_MNT_SHARED(old)) list_add(&mnt->mnt_share, &old->mnt_share); if (IS_MNT_SLAVE(old)) list_add(&mnt->mnt_slave, &old->mnt_slave); mnt->mnt_master = old->mnt_master; } if (flag & CL_MAKE_SHARED) set_mnt_shared(mnt); if (flag & CL_EXPIRE) { if (!list_empty(&old->mnt_expire)) list_add(&mnt->mnt_expire, &old->mnt_expire); } } return mnt; out_free: free_vfsmnt(mnt); return NULL; }clone_mnt()で作成された接続元バーチャルファイルシステムのchild_mnt->mnt_mountpointにbind先ディレクトリdget(dentry)を設定して、bind元バーチャルファイルシステムをbind先ディレクトリにmountします。
void mnt_set_mountpoint(struct mount *mnt, struct dentry *dentry, struct mount *child_mnt) { mnt_add_count(mnt, 1); /* essentially, that's mntget */ child_mnt->mnt_mountpoint = dget(dentry); child_mnt->mnt_parent = mnt; spin_lock(&dentry->d_lock); dentry->d_flags |= DCACHE_MOUNTED; spin_unlock(&dentry->d_lock); } static void commit_tree(struct mount *mnt) { struct mount *parent = mnt->mnt_parent; struct mount *m; LIST_HEAD(head); struct mnt_namespace *n = parent->mnt_ns; BUG_ON(parent == mnt); list_add_tail(&head, &mnt->mnt_list); list_for_each_entry(m, &head, mnt_list) { m->mnt_ns = n; __mnt_make_longterm(m); } list_splice(&head, n->list.prev); list_add_tail(&mnt->mnt_hash, mount_hashtable + hash(&parent->mnt, mnt->mnt_mountpoint)); list_add_tail(&mnt->mnt_child, &parent->mnt_mounts); touch_mnt_namespace(n); }
サンプル
mountポイントの/mnt/loop2のファイルシステムにnoexec等のmountオプションを設定しての運用。[root@localhost north]# ls /mnt/loop1 a.c a.out [root@localhost north]# cat /mnt/loop1/a.c #include <stdio.h> void main() { printf("exec ok\n"); }
[root@localhost north]# mount --bind /mnt/loop1 /mnt/loop2 [root@localhost north]# ls /mnt/loop2 a.c a.out
[root@localhost north]# /mnt/loop1/a.out exec ok [root@localhost north]# /mnt/loop2/a.out exec ok
[root@localhost north]# mount -o remount,noexec /mnt/loop2 [root@localhost north]# /mnt/loop1/a.out exec ok [root@localhost north]# /mnt/loop2/a.out -bash: /mnt/loop2/a.out: 許可がありません
備考
chrootでルートを変更すると、シンボリックリンクではリンクパスでの走査となりchrootのルート配下でないと参照できませんが、bind mountだとchrootのルート配下のパスにmountのmnt_root配下として捜査し、、ハードリンクと同じでchrootのパスの走査が不要で、chrootによる制約がありません。/ets/fstabでのbindオプションで、そのbind元ディレクトリがmount済みである必要があり、デバイスのあるディレクトリ下の構成のみをbind mountして使用する。と言った事はできません。