// Setup sets up a mount point by either mounting the volume if it is // configured, or creating the source directory if supplied. func (m *MountPoint) Setup(mountLabel string) (string, error) { if m.Volume != nil { if m.ID == "" { m.ID = stringid.GenerateNonCryptoID() } return m.Volume.Mount(m.ID) } if len(m.Source) == 0 { return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined") } // system.MkdirAll() produces an error if m.Source exists and is a file (not a directory), if err := system.MkdirAll(m.Source, 0755); err != nil { if perr, ok := err.(*os.PathError); ok { if perr.Err != syscall.ENOTDIR { return "", err } } } if label.RelabelNeeded(m.Mode) { if err := label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil { return "", err } } return m.Source, nil }
// Setup sets up a mount point by either mounting the volume if it is // configured, or creating the source directory if supplied. func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (string, error) { if m.Volume != nil { if m.ID == "" { m.ID = stringid.GenerateNonCryptoID() } path, err := m.Volume.Mount(m.ID) return path, errors.Wrapf(err, "error while mounting volume '%s'", m.Source) } if len(m.Source) == 0 { return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined") } // system.MkdirAll() produces an error if m.Source exists and is a file (not a directory), if m.Type == mounttypes.TypeBind { // idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory) // also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil { if perr, ok := err.(*os.PathError); ok { if perr.Err != syscall.ENOTDIR { return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source) } } } } if label.RelabelNeeded(m.Mode) { if err := label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil { return "", errors.Wrapf(err, "error setting label on mount source '%s'", m.Source) } } return m.Source, nil }
func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination ) if !strings.HasPrefix(dest, rootfs) { dest = filepath.Join(rootfs, dest) } switch m.Device { case "proc", "sysfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } // Selinux kernels do not support labeling of /proc or /sys return mountPropagate(m, rootfs, "") case "mqueue": if err := os.MkdirAll(dest, 0755); err != nil { return err } if err := mountPropagate(m, rootfs, mountLabel); err != nil { // older kernels do not support labeling of /dev/mqueue if err := mountPropagate(m, rootfs, ""); err != nil { return err } } return label.SetFileLabel(dest, mountLabel) case "tmpfs": stat, err := os.Stat(dest) if err != nil { if err := os.MkdirAll(dest, 0755); err != nil { return err } } if err := mountPropagate(m, rootfs, mountLabel); err != nil { return err } if stat != nil { if err = os.Chmod(dest, stat.Mode()); err != nil { return err } } return nil case "devpts": if err := os.MkdirAll(dest, 0755); err != nil { return err } return mountPropagate(m, rootfs, mountLabel) case "securityfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } return mountPropagate(m, rootfs, mountLabel) case "bind": stat, err := os.Stat(m.Source) if err != nil { // error out if the source of a bind mount does not exist as we will be // unable to bind anything to it. return err } // ensure that the destination of the bind mount is resolved of symlinks at mount time because // any previous mounts can invalidate the next mount's destination. // this can happen when a user specifies mounts within other mounts to cause breakouts or other // evil stuff to try to escape the container's rootfs. if dest, err = symlink.FollowSymlinkInScope(filepath.Join(rootfs, m.Destination), rootfs); err != nil { return err } if err := checkMountDestination(rootfs, dest); err != nil { return err } // update the mount with the correct dest after symlinks are resolved. m.Destination = dest if err := createIfNotExists(dest, stat.IsDir()); err != nil { return err } if err := mountPropagate(m, rootfs, mountLabel); err != nil { return err } // bind mount won't change mount options, we need remount to make mount options effective. // first check that we have non-default options required before attempting a remount if m.Flags&^(syscall.MS_REC|syscall.MS_REMOUNT|syscall.MS_BIND) != 0 { // only remount if unique mount options are set if err := remount(m, rootfs); err != nil { return err } } if m.Relabel != "" { if err := label.Validate(m.Relabel); err != nil { return err } shared := label.IsShared(m.Relabel) if err := label.Relabel(m.Source, mountLabel, shared); err != nil { return err } } case "cgroup": binds, err := getCgroupMounts(m) if err != nil { return err } var merged []string for _, b := range binds { ss := filepath.Base(b.Destination) if strings.Contains(ss, ",") { merged = append(merged, ss) } } tmpfs := &configs.Mount{ 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 } for _, b := range binds { if err := mountToRootfs(b, rootfs, mountLabel); err != nil { return err } } // create symlinks for merged cgroups cwd, err := os.Getwd() if err != nil { return err } if err := os.Chdir(filepath.Join(rootfs, m.Destination)); err != nil { return err } for _, mc := range merged { for _, ss := range strings.Split(mc, ",") { if err := os.Symlink(mc, ss); err != nil { // if cgroup already exists, then okay(it could have been created before) if os.IsExist(err) { continue } os.Chdir(cwd) return err } } } if err := os.Chdir(cwd); err != nil { return err } if m.Flags&syscall.MS_RDONLY != 0 { // remount cgroup root as readonly mcgrouproot := &configs.Mount{ Destination: m.Destination, Flags: defaultMountFlags | syscall.MS_RDONLY, } if err := remount(mcgrouproot, rootfs); err != nil { return err } } default: return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination) } return nil }
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. // It follows the next sequence to decide what to mount in each final destination: // // 1. Select the previously configured mount points for the containers, if any. // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. // 4. Cleanup old volumes that are about to be reasigned. func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error { binds := map[string]bool{} mountPoints := map[string]*volume.MountPoint{} // 1. Read already configured mount points. for name, point := range container.MountPoints { mountPoints[name] = point } // 2. Read volumes from other containers. for _, v := range hostConfig.VolumesFrom { containerID, mode, err := volume.ParseVolumesFrom(v) if err != nil { return err } c, err := daemon.Get(containerID) if err != nil { return err } for _, m := range c.MountPoints { cp := &volume.MountPoint{ Name: m.Name, Source: m.Source, RW: m.RW && volume.ReadWrite(mode), Driver: m.Driver, Destination: m.Destination, } if len(cp.Source) == 0 { v, err := daemon.createVolume(cp.Name, cp.Driver, nil) if err != nil { return err } cp.Volume = v } mountPoints[cp.Destination] = cp } } // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 bind, err := volume.ParseMountSpec(b, hostConfig.VolumeDriver) if err != nil { return err } if binds[bind.Destination] { return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination) } if len(bind.Name) > 0 && len(bind.Driver) > 0 { // create the volume v, err := daemon.createVolume(bind.Name, bind.Driver, nil) if err != nil { return err } bind.Volume = v bind.Source = v.Path() // bind.Name is an already existing volume, we need to use that here bind.Driver = v.DriverName() bind = setBindModeIfNull(bind) } if label.RelabelNeeded(bind.Mode) { if err := label.Relabel(bind.Source, container.MountLabel, label.IsShared(bind.Mode)); err != nil { return err } } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } container.Lock() // 4. Cleanup old volumes that are about to be reasigned. for _, m := range mountPoints { if m.BackwardsCompatible() { if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { daemon.volumes.Decrement(mp.Volume) } } } container.MountPoints = mountPoints container.Unlock() return nil }
func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination ) if !strings.HasPrefix(dest, rootfs) { dest = filepath.Join(rootfs, dest) } switch m.Device { case "proc", "sysfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } // Selinux kernels do not support labeling of /proc or /sys return mountPropagate(m, rootfs, "") case "mqueue": if err := os.MkdirAll(dest, 0755); err != nil { return err } if err := mountPropagate(m, rootfs, mountLabel); err != nil { // older kernels do not support labeling of /dev/mqueue if err := mountPropagate(m, rootfs, ""); err != nil { return err } return label.SetFileLabel(dest, mountLabel) } return nil case "tmpfs": copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP tmpDir := "" stat, err := os.Stat(dest) if err != nil { if err := os.MkdirAll(dest, 0755); err != nil { return err } } if copyUp { tmpDir, err = ioutil.TempDir("/tmp", "runctmpdir") if err != nil { return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir") } defer os.RemoveAll(tmpDir) m.Destination = tmpDir } if err := mountPropagate(m, rootfs, mountLabel); err != nil { return err } if copyUp { if err := fileutils.CopyDirectory(dest, tmpDir); err != nil { errMsg := fmt.Errorf("tmpcopyup: failed to copy %s to %s: %v", dest, tmpDir, err) if err1 := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err1 != nil { return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) } return errMsg } if err := syscall.Mount(tmpDir, dest, "", syscall.MS_MOVE, ""); err != nil { errMsg := fmt.Errorf("tmpcopyup: failed to move mount %s to %s: %v", tmpDir, dest, err) if err1 := syscall.Unmount(tmpDir, syscall.MNT_DETACH); err1 != nil { return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg) } return errMsg } } if stat != nil { if err = os.Chmod(dest, stat.Mode()); err != nil { return err } } return nil case "bind": stat, err := os.Stat(m.Source) if err != nil { // error out if the source of a bind mount does not exist as we will be // unable to bind anything to it. return err } // ensure that the destination of the bind mount is resolved of symlinks at mount time because // any previous mounts can invalidate the next mount's destination. // this can happen when a user specifies mounts within other mounts to cause breakouts or other // evil stuff to try to escape the container's rootfs. if dest, err = symlink.FollowSymlinkInScope(filepath.Join(rootfs, m.Destination), rootfs); err != nil { return err } if err := checkMountDestination(rootfs, dest); err != nil { return err } // update the mount with the correct dest after symlinks are resolved. m.Destination = dest if err := createIfNotExists(dest, stat.IsDir()); err != nil { return err } if err := mountPropagate(m, rootfs, mountLabel); err != nil { return err } // bind mount won't change mount options, we need remount to make mount options effective. // first check that we have non-default options required before attempting a remount if m.Flags&^(syscall.MS_REC|syscall.MS_REMOUNT|syscall.MS_BIND) != 0 { // only remount if unique mount options are set if err := remount(m, rootfs); err != nil { return err } } if m.Relabel != "" { if err := label.Validate(m.Relabel); err != nil { return err } shared := label.IsShared(m.Relabel) if err := label.Relabel(m.Source, mountLabel, shared); err != nil { return err } } case "cgroup": binds, err := getCgroupMounts(m) if err != nil { return err } var merged []string for _, b := range binds { ss := filepath.Base(b.Destination) if strings.Contains(ss, ",") { merged = append(merged, ss) } } tmpfs := &configs.Mount{ 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 } for _, b := range binds { if err := mountToRootfs(b, rootfs, mountLabel); err != nil { return err } } for _, mc := range merged { for _, ss := range strings.Split(mc, ",") { // symlink(2) is very dumb, it will just shove the path into // the link and doesn't do any checks or relative path // conversion. Also, don't error out if the cgroup already exists. if err := os.Symlink(mc, filepath.Join(rootfs, m.Destination, ss)); err != nil && !os.IsExist(err) { return err } } } if m.Flags&syscall.MS_RDONLY != 0 { // remount cgroup root as readonly mcgrouproot := &configs.Mount{ Source: m.Destination, Device: "bind", Destination: m.Destination, Flags: defaultMountFlags | syscall.MS_RDONLY | syscall.MS_BIND, } if err := remount(mcgrouproot, rootfs); err != nil { return err } } default: if err := os.MkdirAll(dest, 0755); err != nil { return err } return mountPropagate(m, rootfs, mountLabel) } return nil }
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. // It follows the next sequence to decide what to mount in each final destination: // // 1. Select the previously configured mount points for the containers, if any. // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error { binds := map[string]bool{} mountPoints := map[string]*mountPoint{} // 1. Read already configured mount points. for name, point := range container.MountPoints { mountPoints[name] = point } // 2. Read volumes from other containers. for _, v := range hostConfig.VolumesFrom { containerID, mode, err := parseVolumesFrom(v) if err != nil { return err } c, err := daemon.Get(containerID) if err != nil { return err } for _, m := range c.MountPoints { cp := &mountPoint{ Name: m.Name, Source: m.Source, RW: m.RW && volume.ReadWrite(mode), Driver: m.Driver, Destination: m.Destination, } if len(cp.Source) == 0 { v, err := daemon.createVolume(cp.Name, cp.Driver, nil) if err != nil { return err } cp.Volume = v } mountPoints[cp.Destination] = cp } } // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 bind, err := parseBindMount(b, hostConfig.VolumeDriver) if err != nil { return err } if binds[bind.Destination] { return fmt.Errorf("Duplicate bind mount %s", bind.Destination) } if len(bind.Name) > 0 && len(bind.Driver) > 0 { // create the volume v, err := daemon.createVolume(bind.Name, bind.Driver, nil) if err != nil { return err } bind.Volume = v bind.Source = v.Path() // bind.Name is an already existing volume, we need to use that here bind.Driver = v.DriverName() // Since this is just a named volume and not a typical bind, set to shared mode `z` if bind.Mode == "" { bind.Mode = "z" } } shared := label.IsShared(bind.Mode) if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil { return err } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } // Keep backwards compatible structures bcVolumes := map[string]string{} bcVolumesRW := map[string]bool{} for _, m := range mountPoints { if m.BackwardsCompatible() { bcVolumes[m.Destination] = m.Path() bcVolumesRW[m.Destination] = m.RW // This mountpoint is replacing an existing one, so the count needs to be decremented if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { daemon.volumes.Decrement(mp.Volume) } } } container.Lock() container.MountPoints = mountPoints container.Volumes = bcVolumes container.VolumesRW = bcVolumesRW container.Unlock() return nil }
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) } switch m.Device { case "proc", "sysfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "") 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 { return err } return label.SetFileLabel(dest, mountLabel) case "tmpfs": stat, err := os.Stat(dest) if err != nil { if err := os.MkdirAll(dest, 0755); err != nil { return err } } if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data); err != nil { return err } if stat != nil { if err = os.Chmod(dest, stat.Mode()); err != nil { return err } } return nil case "devpts": if err := os.MkdirAll(dest, 0755); err != nil { return err } return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data) case "securityfs": if err := os.MkdirAll(dest, 0755); err != nil { return err } return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), data) case "bind": stat, err := os.Stat(m.Source) if err != nil { // error out if the source of a bind mount does not exist as we will be // unable to bind anything to it. return err } // ensure that the destination of the bind mount is resolved of symlinks at mount time because // any previous mounts can invalidate the next mount's destination. // this can happen when a user specifies mounts within other mounts to cause breakouts or other // evil stuff to try to escape the container's rootfs. if dest, err = symlink.FollowSymlinkInScope(filepath.Join(rootfs, m.Destination), rootfs); err != nil { return err } if err := checkMountDestination(rootfs, dest); err != nil { return err } 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 { return err } if m.Flags&syscall.MS_RDONLY != 0 { if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), ""); err != nil { return err } } if m.Relabel != "" { if err := label.Validate(m.Relabel); err != nil { return err } shared := label.IsShared(m.Relabel) if err := label.Relabel(m.Source, mountLabel, shared); err != nil { 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 { return err } var merged []string for _, b := range binds { ss := filepath.Base(b.Destination) if strings.Contains(ss, ",") { merged = append(merged, ss) } } tmpfs := &configs.Mount{ Source: "tmpfs", Device: "tmpfs", Destination: m.Destination, Flags: defaultMountFlags, Data: "mode=755", } if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil { return err } for _, b := range binds { if err := mountToRootfs(b, rootfs, mountLabel); err != nil { return err } } // create symlinks for merged cgroups cwd, err := os.Getwd() if err != nil { return err } if err := os.Chdir(filepath.Join(rootfs, m.Destination)); err != nil { return err } for _, mc := range merged { for _, ss := range strings.Split(mc, ",") { if err := os.Symlink(mc, ss); err != nil { // if cgroup already exists, then okay(it could have been created before) if os.IsExist(err) { continue } os.Chdir(cwd) return err } } } if err := os.Chdir(cwd); err != nil { return err } 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 { return err } } default: return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination) } return nil }
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts. // It follows the next sequence to decide what to mount in each final destination: // // 1. Select the previously configured mount points for the containers, if any. // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. // 4. Cleanup old volumes that are about to be reassigned. func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) { binds := map[string]bool{} mountPoints := map[string]*volume.MountPoint{} defer func() { // clean up the container mountpoints once return with error if retErr != nil { for _, m := range mountPoints { if m.Volume == nil { continue } daemon.volumes.Dereference(m.Volume, container.ID) } } }() // 1. Read already configured mount points. for name, point := range container.MountPoints { mountPoints[name] = point } // 2. Read volumes from other containers. for _, v := range hostConfig.VolumesFrom { containerID, mode, err := volume.ParseVolumesFrom(v) if err != nil { return err } c, err := daemon.GetContainer(containerID) if err != nil { return err } for _, m := range c.MountPoints { cp := &volume.MountPoint{ Name: m.Name, Source: m.Source, RW: m.RW && volume.ReadWrite(mode), Driver: m.Driver, Destination: m.Destination, Propagation: m.Propagation, Named: m.Named, } if len(cp.Source) == 0 { v, err := daemon.volumes.GetWithRef(cp.Name, cp.Driver, container.ID) if err != nil { return err } cp.Volume = v } mountPoints[cp.Destination] = cp } } // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 bind, err := volume.ParseMountSpec(b, hostConfig.VolumeDriver) if err != nil { return err } if binds[bind.Destination] { return fmt.Errorf("Duplicate mount point '%s'", bind.Destination) } if len(bind.Name) > 0 { // create the volume v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil) if err != nil { return err } bind.Volume = v bind.Source = v.Path() // bind.Name is an already existing volume, we need to use that here bind.Driver = v.DriverName() bind.Named = true if bind.Driver == "local" { bind = setBindModeIfNull(bind) } } if label.RelabelNeeded(bind.Mode) { if err := label.Relabel(bind.Source, container.MountLabel, label.IsShared(bind.Mode)); err != nil { return err } } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } container.Lock() // 4. Cleanup old volumes that are about to be reassigned. for _, m := range mountPoints { if m.BackwardsCompatible() { if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { daemon.volumes.Dereference(mp.Volume, container.ID) } } } container.MountPoints = mountPoints container.Unlock() return nil }