// AppToSystemdMountUnits prepare bind mount unit for empty or host kind mounting // between stage1 rootfs and chrooted filesystem for application func AppToSystemdMountUnits(root string, appName types.ACName, volumes []types.Volume, ra *schema.RuntimeApp, unitsDir string) error { app := ra.App vols := make(map[types.ACName]types.Volume) for _, v := range volumes { vols[v.Name] = v } mounts, err := initcommon.GenerateMounts(ra, vols) if err != nil { return err } for _, m := range mounts { vol := vols[m.Volume] // source relative to stage1 rootfs to relative pod root whatPath := filepath.Join(stage1MntDir, vol.Name.String()) whatFullPath := filepath.Join(root, whatPath) // destination relative to stage1 rootfs and relative to pod root wherePath := filepath.Join(common.RelAppRootfsPath(appName), m.Path) whereFullPath := filepath.Join(root, wherePath) // assertion to make sure that "what" exists (created earlier by PodToSystemdHostMountUnits) log.Printf("checking required source path: %q", whatFullPath) if _, err := os.Stat(whatFullPath); os.IsNotExist(err) { return fmt.Errorf("bug: missing source for volume %v", vol.Name) } // optionally prepare app directory log.Printf("optionally preparing destination path: %q", whereFullPath) err := os.MkdirAll(whereFullPath, 0700) if err != nil { return fmt.Errorf("failed to prepare dir for mount %v: %v", m.Volume, err) } // install new mount unit for bind mount /mnt/volumeName -> /opt/stage2/{app-id}/rootfs/{{mountPoint.Path}} mu, err := installNewMountUnit( root, // where put a mount unit whatPath, // what - stage1 rootfs /mnt/VolumeName wherePath, // where - inside chroot app filesystem "bind", // fstype "bind", // options serviceUnitName(appName), unitsDir, ) if err != nil { return fmt.Errorf("cannot install new mount unit for app %q: %v", appName.String(), err) } // TODO(iaguis) when we update util-linux to 2.27, this code can go // away and we can bind-mount RO with one unit file. // http://ftp.kernel.org/pub/linux/utils/util-linux/v2.27/v2.27-ReleaseNotes if initcommon.IsMountReadOnly(vol, app.MountPoints) { opts := []*unit.UnitOption{ unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Remount read-only unit for %s", wherePath)), unit.NewUnitOption("Unit", "DefaultDependencies", "false"), unit.NewUnitOption("Unit", "After", mu), unit.NewUnitOption("Unit", "Wants", mu), unit.NewUnitOption("Service", "ExecStart", fmt.Sprintf("/usr/bin/mount -o remount,ro %s", wherePath)), unit.NewUnitOption("Install", "RequiredBy", mu), } remountUnitPath := filepath.Join(root, unitsDir, unit.UnitNamePathEscape(wherePath+"-remount.service")) if err := writeUnit(opts, remountUnitPath); err != nil { return err } } } return nil }
func mountSharedVolumes(root string, p *stage1commontypes.Pod, ra *schema.RuntimeApp) error { app := ra.App appName := ra.Name volumes := p.Manifest.Volumes vols := make(map[types.ACName]types.Volume) for _, v := range volumes { vols[v.Name] = v } sharedVolPath := common.SharedVolumesPath(root) if err := os.MkdirAll(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(errors.New("could not create shared volumes directory"), err) } if err := os.Chmod(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) } imageManifest := p.Images[appName.String()] mounts := stage1initcommon.GenerateMounts(ra, vols, imageManifest) for _, m := range mounts { vol := vols[m.Volume] absRoot, err := filepath.Abs(p.Root) // Absolute path to the pod's rootfs. if err != nil { return errwrap.Wrap(errors.New("could not get pod's root absolute path"), err) } absAppRootfs := common.AppRootfsPath(absRoot, appName) if err != nil { return fmt.Errorf(`could not evaluate absolute path for application rootfs in app: %v`, appName) } mntPath, err := stage1initcommon.EvaluateSymlinksInsideApp(absAppRootfs, m.Path) if err != nil { return errwrap.Wrap(fmt.Errorf("could not evaluate path %v", m.Path), err) } absDestination := filepath.Join(absAppRootfs, mntPath) shPath := filepath.Join(sharedVolPath, vol.Name.String()) if err := stage1initcommon.PrepareMountpoints(shPath, absDestination, &vol, m.DockerImplicit); err != nil { return err } readOnly := stage1initcommon.IsMountReadOnly(vol, app.MountPoints) var source string switch vol.Kind { case "host": source = vol.Source case "empty": source = filepath.Join(common.SharedVolumesPath(root), vol.Name.String()) default: return fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, vol.Kind) } if cleanedSource, err := filepath.EvalSymlinks(source); err != nil { return errwrap.Wrap(fmt.Errorf("could not resolve symlink for source: %v", source), err) } else if err := ensureDestinationExists(cleanedSource, absDestination); err != nil { return errwrap.Wrap(fmt.Errorf("could not create destination mount point: %v", absDestination), err) } else if err := doBindMount(cleanedSource, absDestination, readOnly, vol.Recursive); err != nil { return errwrap.Wrap(fmt.Errorf("could not bind mount path %v (s: %v, d: %v)", m.Path, source, absDestination), err) } } return nil }
// appToNspawnArgs transforms the given app manifest, with the given associated // app name, into a subset of applicable systemd-nspawn argument func (p *Pod) appToNspawnArgs(ra *schema.RuntimeApp) ([]string, error) { var args []string appName := ra.Name app := ra.App sharedVolPath := common.SharedVolumesPath(p.Root) if err := os.MkdirAll(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volumes directory: %v", err) } if err := os.Chmod(sharedVolPath, sharedVolPerm); err != nil { return nil, fmt.Errorf("could not change permissions of %q: %v", sharedVolPath, err) } vols := make(map[types.ACName]types.Volume) for _, v := range p.Manifest.Volumes { vols[v.Name] = v } mounts := initcommon.GenerateMounts(ra, vols) for _, m := range mounts { vol := vols[m.Volume] if vol.Kind == "empty" { if err := os.MkdirAll(filepath.Join(sharedVolPath, vol.Name.String()), sharedVolPerm); err != nil { return nil, fmt.Errorf("could not create shared volume %q: %v", vol.Name, err) } } opt := make([]string, 4) if initcommon.IsMountReadOnly(vol, app.MountPoints) { opt[0] = "--bind-ro=" } else { opt[0] = "--bind=" } switch vol.Kind { case "host": opt[1] = vol.Source case "empty": absRoot, err := filepath.Abs(p.Root) if err != nil { return nil, fmt.Errorf("cannot get pod's root absolute path: %v\n", err) } opt[1] = filepath.Join(common.SharedVolumesPath(absRoot), vol.Name.String()) default: return nil, fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty".`, vol.Kind) } opt[2] = ":" opt[3] = filepath.Join(common.RelAppRootfsPath(appName), m.Path) args = append(args, strings.Join(opt, "")) } for _, i := range app.Isolators { switch v := i.Value().(type) { case types.LinuxCapabilitiesSet: var caps []string // TODO: cleanup the API on LinuxCapabilitiesSet to give strings easily. for _, c := range v.Set() { caps = append(caps, string(c)) } if i.Name == types.LinuxCapabilitiesRetainSetName { capList := strings.Join(caps, ",") args = append(args, "--capability="+capList) } } } return args, nil }
func mountSharedVolumes(root string, p *stage1commontypes.Pod, ra *schema.RuntimeApp) error { app := ra.App appName := ra.Name volumes := p.Manifest.Volumes vols := make(map[types.ACName]types.Volume) for _, v := range volumes { vols[v.Name] = v } sharedVolPath := common.SharedVolumesPath(root) if err := os.MkdirAll(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(errors.New("could not create shared volumes directory"), err) } if err := os.Chmod(sharedVolPath, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) } imageManifest := p.Images[appName.String()] mounts := stage1initcommon.GenerateMounts(ra, vols, imageManifest) for _, m := range mounts { vol := vols[m.Volume] if vol.Kind == "empty" { p := filepath.Join(sharedVolPath, vol.Name.String()) if err := os.MkdirAll(p, stage1initcommon.SharedVolPerm); err != nil { return errwrap.Wrap(fmt.Errorf("could not create shared volume %q", vol.Name), err) } if err := os.Chown(p, *vol.UID, *vol.GID); err != nil { return errwrap.Wrap(fmt.Errorf("could not change owner of %q", p), err) } mod, err := strconv.ParseUint(*vol.Mode, 8, 32) if err != nil { return errwrap.Wrap(fmt.Errorf("invalid mode %q for volume %q", *vol.Mode, vol.Name), err) } if err := os.Chmod(p, os.FileMode(mod)); err != nil { return errwrap.Wrap(fmt.Errorf("could not change permissions of %q", p), err) } } readOnly := stage1initcommon.IsMountReadOnly(vol, app.MountPoints) var source string switch vol.Kind { case "host": source = vol.Source case "empty": source = filepath.Join(common.SharedVolumesPath(root), vol.Name.String()) default: return fmt.Errorf(`invalid volume kind %q. Must be one of "host" or "empty"`, vol.Kind) } absAppRootfs, err := filepath.Abs(common.AppRootfsPath(root, appName)) if err != nil { return fmt.Errorf(`could not evaluate absolute path for application rootfs in app: %v`, appName) } absDestination, err := filepath.Abs(filepath.Join(absAppRootfs, m.Path)) if err != nil { return fmt.Errorf(`could not evaluate absolute path for application volume path %q in: %v`, m.Path, appName) } if !strings.HasPrefix(absDestination, absAppRootfs) { return fmt.Errorf("path escapes app's root: %v", absDestination) } if cleanedSource, err := filepath.EvalSymlinks(source); err != nil { return errwrap.Wrap(fmt.Errorf("could not resolve symlink for source: %v", source), err) } else if err := ensureDestinationExists(cleanedSource, absDestination); err != nil { return errwrap.Wrap(fmt.Errorf("could not create destination mount point: %v", absDestination), err) } else if err := doBindMount(cleanedSource, absDestination, readOnly); err != nil { return errwrap.Wrap(fmt.Errorf("could not bind mount path %v (s: %v, d: %v)", m.Path, source, absDestination), err) } } return nil }