From d1f4a5b8b5c88a98cceebe0bf00b6c186f65345c Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Wed, 16 Sep 2015 15:53:23 -0400 Subject: [PATCH] libcontainer: Allow passing mount propagation flags Right now if one passes a mount propagation flag in spec file, it does not take effect. For example, try following in spec json file. { "type": "bind", "source": "/root/mnt-source", "destination": "/root/mnt-dest", "options": "rbind,shared" } One would expect that /root/mnt-dest will be shared inside the container but that's not the case. #findmnt -o TARGET,PROPAGATION `-/root/mnt-dest private Reason being that propagation flags can't be passed in along with other regular flags. They need to be passed in a separate call to mount syscall. That too, one propagation flag at a time. (from mount man page). Hence, store propagation flags separately in a slice and apply these in that order after the mount call wherever appropriate. This allows user to control the propagation property of mount point inside the container. Storing them separately also solves another problem where recursive flag (syscall.MS_REC) can get mixed up. For example, options "rbind,private" and "bind,rprivate" will be same and there will be no way to differentiate between these if all the flags are stored in a single integer. This patch would allow one to pass propagation flags "[r]shared,[r]slave, [r]private,[r]unbindable" in spec file as per mount property. Signed-off-by: Vivek Goyal --- libcontainer/configs/mount.go | 48 ++++++++++++++++++++++++++++++++++ libcontainer/rootfs_linux.go | 47 ++++++++++++++++----------------- spec.go | 49 +++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 44 deletions(-) diff --git a/libcontainer/configs/mount.go b/libcontainer/configs/mount.go index 50668f04fc9..f982da14de4 100644 --- a/libcontainer/configs/mount.go +++ b/libcontainer/configs/mount.go @@ -1,5 +1,13 @@ package configs +import ( + "path/filepath" + "strings" + "syscall" + + "github.com/opencontainers/runc/libcontainer/label" +) + type Mount struct { // Source path for the mount. Source string `json:"source"` @@ -13,6 +21,9 @@ type Mount struct { // Mount flags. Flags int `json:"flags"` + // Propagation Flags + PropagationFlags []int `json:"propagation_flags"` + // Mount data applied to the mount. Data string `json:"data"` @@ -25,3 +36,40 @@ type Mount struct { // Optional Command to be run after Source is mounted. PostmountCmds []Command `json:"postmount_cmds"` } + +func (m *Mount) Remount(rootfs string) error { + var ( + dest = m.Destination + ) + if !strings.HasPrefix(dest, rootfs) { + dest = filepath.Join(rootfs, dest) + } + + if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), ""); err != nil { + return err + } + return nil +} + +// Do the mount operation followed by additional mounts required to take care +// of propagation flags. +func (m *Mount) MountPropagate(rootfs string, mountLabel string) error { + var ( + dest = m.Destination + data = label.FormatMountLabel(m.Data, mountLabel) + ) + if !strings.HasPrefix(dest, rootfs) { + dest = filepath.Join(rootfs, dest) + } + + if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data); err != nil { + return err + } + + for _, pflag := range m.PropagationFlags { + if err := syscall.Mount("", dest, "", uintptr(pflag), ""); err != nil { + return err + } + } + return nil +} diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index a5602d1bb70..51f961dfaf4 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -96,7 +96,6 @@ func mountCmd(cmd configs.Command) error { func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination - data = label.FormatMountLabel(m.Data, mountLabel) ) if !strings.HasPrefix(dest, rootfs) { dest = filepath.Join(rootfs, dest) @@ -107,12 +106,12 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { if err := os.MkdirAll(dest, 0755); err != nil { return err } - return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "") + return m.MountPropagate(rootfs, mountLabel) case "mqueue": if err := os.MkdirAll(dest, 0755); err != nil { return err } - if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), ""); err != nil { + if err := m.MountPropagate(rootfs, mountLabel); err != nil { return err } return label.SetFileLabel(dest, mountLabel) @@ -123,7 +122,7 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { return err } } - if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data); err != nil { + if err := m.MountPropagate(rootfs, mountLabel); err != nil { return err } if stat != nil { @@ -136,12 +135,12 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { if err := os.MkdirAll(dest, 0755); err != nil { return err } - return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data) + return m.MountPropagate(rootfs, mountLabel) case "securityfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } - return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data) + return m.MountPropagate(rootfs, mountLabel) case "bind": stat, err := os.Stat(m.Source) if err != nil { @@ -162,11 +161,11 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { if err := createIfNotExists(dest, stat.IsDir()); err != nil { return err } - if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data); err != nil { + if err := m.MountPropagate(rootfs, mountLabel); err != nil { return err } // bind mount won't change mount options, we need remount to make mount options effective. - if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), ""); err != nil { + if err := m.Remount(rootfs); err != nil { return err } if m.Relabel != "" { @@ -178,11 +177,6 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { return err } } - if m.Flags&syscall.MS_PRIVATE != 0 { - if err := syscall.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { - return err - } - } case "cgroup": binds, err := getCgroupMounts(m) if err != nil { @@ -196,11 +190,12 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { } } tmpfs := &configs.Mount{ - Source: "tmpfs", - Device: "tmpfs", - Destination: m.Destination, - Flags: defaultMountFlags, - Data: "mode=755", + Source: "tmpfs", + Device: "tmpfs", + Destination: m.Destination, + Flags: defaultMountFlags, + Data: "mode=755", + PropagationFlags: m.PropagationFlags, } if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil { return err @@ -235,8 +230,11 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { } if m.Flags&syscall.MS_RDONLY != 0 { // remount cgroup root as readonly - rootfsCgroup := filepath.Join(rootfs, m.Destination) - if err := syscall.Mount("", rootfsCgroup, "", defaultMountFlags|syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { + mcgrouproot := &configs.Mount{ + Destination: m.Destination, + Flags: defaultMountFlags | syscall.MS_RDONLY, + } + if err := mcgrouproot.Remount(rootfs); err != nil { return err } } @@ -269,10 +267,11 @@ func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) { return nil, err } binds = append(binds, &configs.Mount{ - Device: "bind", - Source: filepath.Join(mm.Mountpoint, relDir), - Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")), - Flags: syscall.MS_BIND | syscall.MS_REC | m.Flags, + Device: "bind", + Source: filepath.Join(mm.Mountpoint, relDir), + Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")), + Flags: syscall.MS_BIND | syscall.MS_REC | m.Flags, + PropagationFlags: m.PropagationFlags, }) } diff --git a/spec.go b/spec.go index 892dc938215..ff9e5c941b6 100644 --- a/spec.go +++ b/spec.go @@ -387,7 +387,7 @@ func createLibcontainerConfig(cgroupName string, spec *specs.LinuxSpec, rspec *s } func createLibcontainerMount(cwd, dest string, m specs.Mount) *configs.Mount { - flags, data := parseMountOptions(m.Options) + flags, pgflags, data := parseMountOptions(m.Options) source := m.Source if m.Type == "bind" { if !filepath.IsAbs(source) { @@ -395,11 +395,12 @@ func createLibcontainerMount(cwd, dest string, m specs.Mount) *configs.Mount { } } return &configs.Mount{ - Device: m.Type, - Source: source, - Destination: dest, - Data: data, - Flags: flags, + Device: m.Type, + Source: source, + Destination: dest, + Data: data, + Flags: flags, + PropagationFlags: pgflags, } } @@ -519,12 +520,13 @@ func createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) { }, nil } -// parseMountOptions parses the string and returns the flags and any mount data that -// it contains. -func parseMountOptions(options []string) (int, string) { +// parseMountOptions parses the string and returns the flags, propagation +// flags and any mount data that it contains. +func parseMountOptions(options []string) (int, []int, string) { var ( - flag int - data []string + flag int + pgflag []int + data []string ) flags := map[string]struct { clear bool @@ -547,22 +549,27 @@ func parseMountOptions(options []string) (int, string) { "norelatime": {true, syscall.MS_RELATIME}, "nostrictatime": {true, syscall.MS_STRICTATIME}, "nosuid": {false, syscall.MS_NOSUID}, - "private": {false, syscall.MS_PRIVATE}, "rbind": {false, syscall.MS_BIND | syscall.MS_REC}, "relatime": {false, syscall.MS_RELATIME}, "remount": {false, syscall.MS_REMOUNT}, "ro": {false, syscall.MS_RDONLY}, - "rprivate": {false, syscall.MS_PRIVATE | syscall.MS_REC}, - "rshared": {false, syscall.MS_SHARED | syscall.MS_REC}, - "rslave": {false, syscall.MS_SLAVE | syscall.MS_REC}, - "runbindable": {false, syscall.MS_UNBINDABLE | syscall.MS_REC}, "rw": {true, syscall.MS_RDONLY}, - "shared": {false, syscall.MS_SHARED}, - "slave": {false, syscall.MS_SLAVE}, "strictatime": {false, syscall.MS_STRICTATIME}, "suid": {true, syscall.MS_NOSUID}, "sync": {false, syscall.MS_SYNCHRONOUS}, - "unbindable": {false, syscall.MS_UNBINDABLE}, + } + propagationFlags := map[string]struct { + clear bool + flag int + }{ + "private": {false, syscall.MS_PRIVATE}, + "shared": {false, syscall.MS_SHARED}, + "slave": {false, syscall.MS_SLAVE}, + "unbindable": {false, syscall.MS_UNBINDABLE}, + "rprivate": {false, syscall.MS_PRIVATE | syscall.MS_REC}, + "rshared": {false, syscall.MS_SHARED | syscall.MS_REC}, + "rslave": {false, syscall.MS_SLAVE | syscall.MS_REC}, + "runbindable": {false, syscall.MS_UNBINDABLE | syscall.MS_REC}, } for _, o := range options { // If the option does not exist in the flags table or the flag @@ -574,11 +581,13 @@ func parseMountOptions(options []string) (int, string) { } else { flag |= f.flag } + } else if f, exists := propagationFlags[o]; exists && f.flag != 0 { + pgflag = append(pgflag, f.flag) } else { data = append(data, o) } } - return flag, strings.Join(data, ",") + return flag, pgflag, strings.Join(data, ",") } func setupSeccomp(config *specs.Seccomp) (*configs.Seccomp, error) {