首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

应用挂载名称空间(3)

应用挂载名称空间(3)

用户专用根目录的系统设置您已经看到了用户专用私有挂载树的实现细节,包括在登录时必须执行的操作。在本节中,将看到在创建用户帐户和系统引导时使用的完整脚本。
清单 4 给出在创建用户时运行的脚本。
清单 4. 用于创建用户的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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/ 下面。这个递归复制的根文件系统树成为这个用户专用的文件系统,可以持久地保留这个用户执行的挂载活动(对多次登录有效,但是在重新启动之后就会失效)。
这个复制的树是根树的从属挂载,所以根树中的挂载活动会传播到这个拷贝,但是不会反向传播。这个树被标为共享的,所以后续的拷贝(也就是通过名称空间克隆建立的拷贝)是相互共享的挂载;任何拷贝中的挂载活动都会传播到所有其他拷贝。
最后,创建一个私有的挂载 __my_private_mnt__。这是为了帮助 pivot_root()(见清单 6)在删除树之前临时准备根挂载。目前不需要太关注它。了解了 pivot_root() 的语义之后,这个步骤的意义就会明确了。目前只需记住,如果准备的挂载是共享的,那么 pivot 挂载就不会成功。
清单 5 给出在系统引导时运行的脚本。
清单 5. 在引导时执行系统初始化的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 上的挂载标为不可绑定的。对于每个用户,都会递归地复制整个挂载树,所以在 /user/$user_1 下面创建第一个用户的拷贝之后,在 /user/$user_2 下面创建的拷贝会包含 /user/$user_1 的递归拷贝(/user/$user_2/user/$user_1)。可以想像到,这会快速地消耗大量内存。将 /user 标为不可绑定的,就可以防止在递归地绑定挂载 / 时复制 /user。
最后,为每个用户执行一次  中的脚本。如果 /user/$user 目录不存在,就创建这个目录,并按照前面的描述设置适当的挂载传播。
清单 6 给出在用户登录时执行的 PAM 模块的片段。
清单 6. 用户登录所用的 PAM 代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#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__ 标为私有的,使后面的卸载操作不会传播到根挂载树的其他拷贝,包括原来的树。
最后,从 __my_private_mnt__ 卸载原来的根。
在用户创建脚本(见 )中,将 __my_private_mnt__ 目录设置为私有的挂载,并指出这是为了辅助 pivot_root() 的操作。这么做实际上是由于 pivot_root() 有一个文档中没有记载的限制,这个限制与旧根和新根的挂载传播状态有关。要想让 pivot_root() 成功执行,以下挂载不能是共享对象:
  • 旧根的目标位置
  • 新根的当前父位置(在调用 pivot_root() 的时候)
  • 新根的目标父位置
在  中接近末尾的地方将 __my_private_mnt__ 标为私有的,就可以满足第一个条件。第二个条件已经满足了,因为新根的当前父位置是 /user,/user 上的挂载是一个不可绑定的挂载。第三个条件也已经满足了,因为新根的目标父位置就是当前根的父位置。这个挂载是一个不可见的 rootfs 挂载,它已经是私有的。
在本节中,讨论了如何实现每个用户专用的挂载树,让挂载事件在一个用户的所有登录会话之间共享,但是对其他用户隐藏。在下一节中,讨论如何允许用户相互共享挂载树。
返回列表