diff --git a/snapshots/overlay/overlay.go b/snapshots/overlay/overlay.go index bc4597d8f..bc3404afb 100644 --- a/snapshots/overlay/overlay.go +++ b/snapshots/overlay/overlay.go @@ -268,16 +268,22 @@ func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snap // This can be used to recover mounts after calling View or Prepare. func (o *snapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount, err error) { var s storage.Snapshot + var info snapshots.Info if err := o.ms.WithTransaction(ctx, false, func(ctx context.Context) error { s, err = storage.GetSnapshot(ctx, key) if err != nil { return fmt.Errorf("failed to get active mount: %w", err) } + + _, info, _, err = storage.GetInfo(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot info: %w", err) + } return nil }); err != nil { return nil, err } - return o.mounts(s), nil + return o.mounts(s, info), nil } func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { @@ -411,10 +417,46 @@ func (o *snapshotter) getCleanupDirectories(ctx context.Context) ([]string, erro return cleanup, nil } +func validateIDMapping(mapping string) error { + var ( + hostID int + ctrID int + length int + ) + + if _, err := fmt.Sscanf(mapping, "%d:%d:%d", &ctrID, &hostID, &length); err != nil { + return err + } + // Almost impossible, but snapshots.WithLabels doesn't check it + if ctrID < 0 || hostID < 0 || length < 0 { + return fmt.Errorf("invalid mapping \"%d:%d:%d\"", ctrID, hostID, length) + } + if ctrID != 0 { + return fmt.Errorf("container mapping of 0 is only supported") + } + return nil +} + +func hostID(mapping string) (int, error) { + var ( + hostID int + ctrID int + length int + ) + if err := validateIDMapping(mapping); err != nil { + return -1, fmt.Errorf("invalid mapping: %w", err) + } + if _, err := fmt.Sscanf(mapping, "%d:%d:%d", &ctrID, &hostID, &length); err != nil { + return -1, err + } + return hostID, nil +} + func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) { var ( s storage.Snapshot td, path string + info snapshots.Info ) defer func() { @@ -445,14 +487,46 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k return fmt.Errorf("failed to create snapshot: %w", err) } - if len(s.ParentIDs) > 0 { - st, err := os.Stat(o.upperPath(s.ParentIDs[0])) - if err != nil { - return fmt.Errorf("failed to stat parent: %w", err) - } + _, info, _, err = storage.GetInfo(ctx, key) + if err != nil { + return fmt.Errorf("failed to get snapshot info: %w", err) + } - stat := st.Sys().(*syscall.Stat_t) - if err := os.Lchown(filepath.Join(td, "fs"), int(stat.Uid), int(stat.Gid)); err != nil { + mappedUID := -1 + mappedGID := -1 + // NOTE: if idmapped mounts' supported by hosted kernel there may be + // no parents at all, so overlayfs will not work and snapshotter + // will use bind mount. To be able to create file objects inside the + // rootfs -- just chown this only bound directory according to provided + // {uid,gid}map. In case of one/multiple parents -- chown upperdir. + if v, ok := info.Labels[snapshots.LabelSnapshotUIDMapping]; ok { + if mappedUID, err = hostID(v); err != nil { + return fmt.Errorf("failed to parse UID mapping: %w", err) + } + } + if v, ok := info.Labels[snapshots.LabelSnapshotGIDMapping]; ok { + if mappedGID, err = hostID(v); err != nil { + return fmt.Errorf("failed to parse GID mapping: %w", err) + } + } + + if mappedUID == -1 || mappedGID == -1 { + if len(s.ParentIDs) > 0 { + st, err := os.Stat(o.upperPath(s.ParentIDs[0])) + if err != nil { + return fmt.Errorf("failed to stat parent: %w", err) + } + stat, ok := st.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("incompatible types after stat call: *syscall.Stat_t expected") + } + mappedUID = int(stat.Uid) + mappedGID = int(stat.Gid) + } + } + + if mappedUID != -1 && mappedGID != -1 { + if err := os.Lchown(filepath.Join(td, "fs"), mappedUID, mappedGID); err != nil { return fmt.Errorf("failed to chown: %w", err) } } @@ -467,8 +541,7 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k }); err != nil { return nil, err } - - return o.mounts(s), nil + return o.mounts(s, info), nil } func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) { @@ -490,7 +563,18 @@ func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, return td, nil } -func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { +func (o *snapshotter) mounts(s storage.Snapshot, info snapshots.Info) []mount.Mount { + var options []string + + if o.remapIds { + if v, ok := info.Labels[snapshots.LabelSnapshotUIDMapping]; ok { + options = append(options, fmt.Sprintf("uidmap=%s", v)) + } + if v, ok := info.Labels[snapshots.LabelSnapshotGIDMapping]; ok { + options = append(options, fmt.Sprintf("gidmap=%s", v)) + } + } + if len(s.ParentIDs) == 0 { // if we only have one layer/no parents then just return a bind mount as overlay // will not work @@ -498,20 +582,18 @@ func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { if s.Kind == snapshots.KindView { roFlag = "ro" } - return []mount.Mount{ { Source: o.upperPath(s.ID), Type: "bind", - Options: []string{ + Options: append(options, roFlag, "rbind", - }, + ), }, } } - options := o.options if s.Kind == snapshots.KindActive { options = append(options, fmt.Sprintf("workdir=%s", o.workPath(s.ID)), @@ -522,10 +604,10 @@ func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { { Source: o.upperPath(s.ParentIDs[0]), Type: "bind", - Options: []string{ + Options: append(options, "ro", "rbind", - }, + ), }, } } @@ -534,8 +616,9 @@ func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { for i := range s.ParentIDs { parentPaths[i] = o.upperPath(s.ParentIDs[i]) } - options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":"))) + options = append(options, o.options...) + return []mount.Mount{ { Type: "overlay", @@ -543,7 +626,6 @@ func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { Options: options, }, } - } func (o *snapshotter) upperPath(id string) string {