Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,8 @@ func ReadInitSpec(r *v1.RunConfig, flags *pflag.FlagSet) (*v1.InitSpec, error) {
}

func ReadMountSpec(r *v1.RunConfig, flags *pflag.FlagSet) (*v1.MountSpec, error) {
mount, err := config.NewMountSpec(r.Config)
if err != nil {
r.Logger.Errorf("Failed preparing mount configuration: %s", err)
return nil, err
}
mount := config.NewMountSpec()

vp := viper.Sub("mount")
if vp == nil {
vp = viper.New()
Expand All @@ -267,7 +264,7 @@ func ReadMountSpec(r *v1.RunConfig, flags *pflag.FlagSet) (*v1.MountSpec, error)
// Bind mount env vars
viperReadEnv(vp, "MOUNT", constants.GetMountKeyEnvMap())

err = vp.Unmarshal(mount, setDecoder, decodeHook)
err := vp.Unmarshal(mount, setDecoder, decodeHook)
if err != nil {
r.Logger.Warnf("error unmarshalling MountSpec: %s", err)
return mount, err
Expand Down Expand Up @@ -324,7 +321,21 @@ func applyKernelCmdline(r *v1.RunConfig, mount *v1.MountSpec) error {
return err
}
case "elemental.oemlabel", "rd.cos.oemlabel":
mount.Partitions.OEM.FilesystemLabel = val
oemdev := fmt.Sprintf("LABEL=%s", val)
var mnt *v1.VolumeMount
for _, mnt = range mount.Volumes {
if mnt.Mountpoint == constants.OEMPath {
mnt.Device = oemdev
break
}
}
if mnt == nil {
mount.Volumes = append(mount.Volumes, &v1.VolumeMount{
Mountpoint: constants.OEMPath,
Device: oemdev,
Options: []string{"rw", "defaults"},
})
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ upgrade:
mount:
sysroot: /sysroot # Path to mount system to
write-fstab: true # Write fstab into sysroot/etc/fstab
extra-volumes:
- mountpoint: /run/elemental/efi
device: PARTLABEL=efi
options: ["ro", "defaults"]
- mountpoint: /oem
device: LABEL=COS_OEM
options: ["defaults"]
ephemeral:
type: tmpfs # tmpfs|block
device: /dev/sda6 # Block device used to store overlay. Used when type is set to block
Expand All @@ -134,6 +141,10 @@ mount:
- /srv
persistent:
mode: overlay # overlay|bind
volume:
mountpoint: /run/elemental/persistent
device: PARTLABEL=persistent
options: ["defaults"]
paths:
- /etc/systemd
- /etc/ssh
Expand Down
215 changes: 172 additions & 43 deletions pkg/action/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,66 @@ limitations under the License.
package action

import (
"bufio"
"fmt"
"path/filepath"
"regexp"
"sort"
"strings"

"github.com/hashicorp/go-multierror"
"github.com/rancher/elemental-toolkit/pkg/constants"
"github.com/rancher/elemental-toolkit/pkg/elemental"
v1 "github.com/rancher/elemental-toolkit/pkg/types/v1"
"github.com/rancher/elemental-toolkit/pkg/utils"
)

const overlaySuffix = ".overlay"
const labelPref = "LABEL="
const partLabelPref = "PARTLABEL="
const uuidPref = "UUID="
const devPref = "/dev/"
const diskBy = "/dev/disk/by-"
const diskByLabel = diskBy + "label"
const diskByPartLabel = diskBy + "partlabel"
const diskByUUID = diskBy + "uuid"
const runPath = "/run"

func RunMount(cfg *v1.RunConfig, spec *v1.MountSpec) error {
var fstabData string
var err error

cfg.Logger.Info("Running mount command")

cfg.Logger.Debug("Mounting elemental partitions")
if err := elemental.MountPartitions(cfg.Config, spec.Partitions.PartitionsByMountPoint(false)); err != nil {
cfg.Logger.Errorf("Error mounting elemental partitions: %s", err.Error())
if spec.WriteFstab {
cfg.Logger.Debug("Generating inital sysroot fstab lines")
fstabData, err = InitialFstabData(cfg.Runner, spec.Sysroot)
if err != nil {
cfg.Logger.Errorf("Error mounting volumes: %s", err.Error())
return err
}

}

cfg.Logger.Debug("Mounting volumes")
if err = MountVolumes(cfg, spec); err != nil {
cfg.Logger.Errorf("Error mounting volumes: %s", err.Error())
return err
}

cfg.Logger.Debugf("Mounting ephemeral directories")
if err := MountEphemeral(cfg, spec.Sysroot, spec.Ephemeral); err != nil {
if err = MountEphemeral(cfg, spec.Sysroot, spec.Ephemeral); err != nil {
cfg.Logger.Errorf("Error mounting overlays: %s", err.Error())
return err
}

if ok, _ := elemental.IsMounted(cfg.Config, spec.Partitions.Persistent); ok {
cfg.Logger.Debugf("Mounting persistent directories")
if err := MountPersistent(cfg, spec.Sysroot, spec.Persistent); err != nil {
cfg.Logger.Errorf("Error mounting persistent overlays: %s", err.Error())
return err
}
} else {
cfg.Logger.Warn("No persistent partition defined or mounted, omitting any persistent paths configuration")
cfg.Logger.Debugf("Mounting persistent directories")
if err = MountPersistent(cfg, spec.Sysroot, spec.Persistent); err != nil {
cfg.Logger.Errorf("Error mounting persistent overlays: %s", err.Error())
return err
}

cfg.Logger.Debugf("Writing fstab")
if err := WriteFstab(cfg, spec); err != nil {
if err = WriteFstab(cfg, spec, fstabData); err != nil {
cfg.Logger.Errorf("Error writing new fstab: %s", err.Error())
return err
}
Expand All @@ -64,6 +85,66 @@ func RunMount(cfg *v1.RunConfig, spec *v1.MountSpec) error {
return nil
}

func MountVolumes(cfg *v1.RunConfig, spec *v1.MountSpec) error {
var errs error

volumes := map[string]*v1.VolumeMount{}
keys := []string{}
if spec.HasPersistent() {
volumes[spec.Persistent.Volume.Mountpoint] = &spec.Persistent.Volume
keys = append(keys, spec.Persistent.Volume.Mountpoint)
}

for _, v := range spec.Volumes {
volumes[v.Mountpoint] = v
keys = append(keys, v.Mountpoint)
}

sort.Strings(keys)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case you are wondering this sorting keys logic to allow nested mountpoints such as having a volume mounted at /run/elemental and persistent mount point at /run/elemental/persistent. With this logic we make sure /run/elemental is mounted first.


for _, k := range keys {
var dev string
switch {
case strings.HasPrefix(volumes[k].Device, labelPref):
dev = filepath.Join(diskByLabel, strings.TrimPrefix(volumes[k].Device, labelPref))
case strings.HasPrefix(volumes[k].Device, partLabelPref):
dev = filepath.Join(diskByPartLabel, strings.TrimPrefix(volumes[k].Device, partLabelPref))
case strings.HasPrefix(volumes[k].Device, uuidPref):
dev = filepath.Join(diskByUUID, strings.TrimPrefix(volumes[k].Device, uuidPref))
case strings.HasPrefix(volumes[k].Device, devPref):
dev = volumes[k].Device
default:
cfg.Logger.Errorf("Unknown device reference, it should be LABEL, PARTLABEL, UUID or a /dev/* path")
errs = multierror.Append(errs, fmt.Errorf("Unkown device reference: %s", volumes[k].Device))
continue
}
mountpoint := volumes[k].Mountpoint
if !strings.HasPrefix(mountpoint, runPath) {
mountpoint = filepath.Join(spec.Sysroot, mountpoint)
}

err := utils.MkdirAll(cfg.Fs, mountpoint, constants.DirPerm)
if err != nil {
cfg.Logger.Errorf("failed creating mountpoint %s", mountpoint)
errs = multierror.Append(errs, err)
continue
}

fstype := volumes[k].FSType
if fstype == "" {
fstype = "auto"
}

cfg.Logger.Debugf("Mounting %s to %s", dev, mountpoint)
err = cfg.Mounter.Mount(dev, mountpoint, fstype, volumes[k].Options)
if err != nil {
cfg.Logger.Errorf("failed mounting device %s to %s", dev, mountpoint)
errs = multierror.Append(errs, err)
}
}
return errs
}

func MountEphemeral(cfg *v1.RunConfig, sysroot string, overlay v1.EphemeralMounts) error {
if err := utils.MkdirAll(cfg.Config.Fs, constants.OverlayDir, constants.DirPerm); err != nil {
cfg.Logger.Errorf("Error creating directory %s: %s", constants.OverlayDir, err.Error())
Expand Down Expand Up @@ -111,10 +192,16 @@ func MountPersistent(cfg *v1.RunConfig, sysroot string, persistent v1.Persistent
mountFunc = MountBindPath
}

if persistent.Volume.Device == "" || persistent.Volume.Mountpoint == "" {
cfg.Logger.Debug("No persistent device defined, omitting persistent paths mounts")
return nil
}

for _, path := range persistent.Paths {
cfg.Logger.Debugf("Mounting path %s into %s", path, sysroot)

if err := mountFunc(cfg, sysroot, constants.PersistentStateDir, path); err != nil {
target := filepath.Join(persistent.Volume.Mountpoint, constants.PersistentStateDir)
if err := mountFunc(cfg, sysroot, target, path); err != nil {
cfg.Logger.Errorf("Error mounting path %s: %s", path, err.Error())
return err
}
Expand Down Expand Up @@ -192,68 +279,110 @@ func MountOverlayPath(cfg *v1.RunConfig, sysroot, overlayDir, path string) error
return nil
}

func findmnt(runner v1.Runner, mountpoint string) (string, error) {
output, err := runner.Run("findmnt", "-fno", "SOURCE", mountpoint)
return strings.TrimSuffix(string(output), "\n"), err
}
func WriteFstab(cfg *v1.RunConfig, spec *v1.MountSpec, data string) error {
var errs error

func WriteFstab(cfg *v1.RunConfig, spec *v1.MountSpec) error {
if !spec.WriteFstab {
cfg.Logger.Debug("Skipping writing fstab")
return nil
}

loop, err := findmnt(cfg.Runner, spec.Sysroot)
if err != nil {
return err
for _, vol := range spec.Volumes {
data += fstab(vol.Device, vol.Mountpoint, vol.FSType, vol.Options)
}

data := fstab(loop, "/", "ext2", []string{"ro", "relatime"})
data = data + fstab("tmpfs", constants.OverlayDir, "tmpfs", []string{"defaults", fmt.Sprintf("size=%s", spec.Ephemeral.Size)})
if spec.HasPersistent() {
pVol := spec.Persistent.Volume
data += fstab(pVol.Device, pVol.Mountpoint, pVol.FSType, pVol.Options)

for _, part := range spec.Partitions.PartitionsByMountPoint(false) {
if part.Path == "" {
cfg.Logger.Warnf("Partition '%s' has undefined device, can't be included in fstab", part.Name)
continue
}

data = data + fstab(part.Path, part.MountPoint, "auto", part.Flags)
}

for _, rw := range spec.Ephemeral.Paths {
data += overlayLine(rw, constants.OverlayDir, constants.OverlayDir)
}

if ok, _ := elemental.IsMounted(cfg.Config, spec.Partitions.Persistent); ok {
for _, path := range spec.Persistent.Paths {
if spec.Persistent.Mode == constants.OverlayMode {
data += overlayLine(path, constants.PersistentStateDir, constants.PersistentDir)
data += overlayLine(path, filepath.Join(pVol.Mountpoint, constants.PersistentStateDir), constants.PersistentDir)
continue
}

if spec.Persistent.Mode == constants.BindMode {
trimmed := strings.TrimPrefix(path, "/")
pathName := strings.ReplaceAll(trimmed, "/", "-") + ".bind"
stateDir := fmt.Sprintf("%s/%s", constants.PersistentStateDir, pathName)
stateDir := filepath.Join(pVol.Mountpoint, constants.PersistentStateDir, pathName)

data = data + fstab(stateDir, path, "none", []string{"defaults", "bind"})
continue
}

return fmt.Errorf("Unknown persistent mode '%s'", spec.Persistent.Mode)
errs = multierror.Append(errs, fmt.Errorf("Unknown persistent mode '%s'", spec.Persistent.Mode))
}
}

data += fstab("tmpfs", constants.OverlayDir, "tmpfs", []string{"defaults", fmt.Sprintf("size=%s", spec.Ephemeral.Size)})
for _, rw := range spec.Ephemeral.Paths {
data += overlayLine(rw, constants.OverlayDir, constants.OverlayDir)
}

return cfg.Config.Fs.WriteFile(filepath.Join(spec.Sysroot, "/etc/fstab"), []byte(data), 0644)
}

func InitialFstabData(runner v1.Runner, sysroot string) (string, error) {
var data string

mounts, err := findmnt(runner, "/")
if err != nil {
return "", err
}
for _, mnt := range mounts {
if mnt.Mountpoint == sysroot {
data += fstab(mnt.Device, "/", "auto", mnt.Options)
} else if strings.HasPrefix(mnt.Mountpoint, sysroot) {
data += fstab(mnt.Device, strings.TrimPrefix(mnt.Mountpoint, sysroot), mnt.FSType, mnt.Options)
} else if strings.HasPrefix(mnt.Mountpoint, constants.RunElementalDir) {
data += fstab(mnt.Device, mnt.Mountpoint, mnt.FSType, mnt.Options)
} else if mnt.Mountpoint == constants.RunningStateDir {
data += fstab(mnt.Device, mnt.Mountpoint, mnt.FSType, mnt.Options)
}
}

return data, nil
}

func fstab(device, path, fstype string, flags []string) string {
if len(flags) == 0 {
flags = []string{"defaults"}
}

if fstype == "" {
fstype = "auto"
}
return fmt.Sprintf("%s\t%s\t%s\t%s\t0\t0\n", device, path, fstype, strings.Join(flags, ","))
}

func findmnt(runner v1.Runner, mountpoint string) ([]*v1.VolumeMount, error) {
mounts := []*v1.VolumeMount{}
output, err := runner.Run("findmnt", "-Rrfno", "SOURCE,TARGET,FSTYPE,OPTIONS", mountpoint)
if err != nil {
return nil, err
}

scanner := bufio.NewScanner(strings.NewReader(strings.TrimSpace(string(output))))
for scanner.Scan() {
lineFields := strings.Fields(scanner.Text())
if len(lineFields) != 4 {
continue
}
if lineFields[2] == "btrfs" {
r := regexp.MustCompile(`(/.+)\[.*\]`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On btrfs mounted subvolume added in brackets something like /dev/sda1[/@/.snapshots/1/snapshot] hence we need to remove it from the SOURCE field, that's why there this extra parsing logic here.

if r.MatchString(lineFields[0]) {
match := r.FindStringSubmatch(lineFields[0])
lineFields[0] = match[1]
}
}
mounts = append(mounts, &v1.VolumeMount{
Device: lineFields[0],
Mountpoint: lineFields[1],
Options: strings.Split(lineFields[3], ","),
})
}
return mounts, nil
}

func overlayLine(path, upperPath, requriedMount string) string {
trimmed := strings.TrimPrefix(path, "/")
pathName := strings.ReplaceAll(trimmed, "/", "-") + overlaySuffix
Expand Down
Loading