mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
When files are hardlinked, the inodes only need to be chowned once. Signed-off-by: Derek McGowan <derek@mcg.dev>
163 lines
4.1 KiB
Go
163 lines
4.1 KiB
Go
//go:build !windows
|
|
|
|
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
|
|
"github.com/containerd/containerd/v2/core/mount"
|
|
"github.com/containerd/containerd/v2/core/snapshots"
|
|
"github.com/containerd/continuity/fs"
|
|
"github.com/containerd/continuity/sysx"
|
|
)
|
|
|
|
const (
|
|
// Values based on linux/include/uapi/linux/capability.h
|
|
xattrCapsSz2 = 20
|
|
versionOffset = 3
|
|
vfsCapRevision2 = 2
|
|
vfsCapRevision3 = 3
|
|
remapSuffix = "-remap"
|
|
)
|
|
|
|
func (i *ImageService) remapSnapshot(ctx context.Context, snapshotter snapshots.Snapshotter, id string, parentSnapshot string) error {
|
|
_, err := snapshotter.Prepare(ctx, id, parentSnapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mounts, err := snapshotter.Mounts(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := i.remapRootFS(ctx, mounts); err != nil {
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (i *ImageService) remapRootFS(ctx context.Context, mounts []mount.Mount) error {
|
|
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if stat == nil {
|
|
return fmt.Errorf("cannot get underlying data for %s", path)
|
|
}
|
|
|
|
uid, gid, err := i.idMapping.ToHost(int(stat.Uid), int(stat.Gid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return chownWithCaps(path, uid, gid)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (i *ImageService) copyAndUnremapRootFS(ctx context.Context, dst, src []mount.Mount) error {
|
|
return mount.WithTempMount(ctx, src, func(source string) error {
|
|
return mount.WithTempMount(ctx, dst, func(root string) error {
|
|
// TODO: Update CopyDir to support remap directly
|
|
if err := fs.CopyDir(root, source); err != nil {
|
|
return fmt.Errorf("failed to copy: %w", err)
|
|
}
|
|
|
|
inos := make(map[uint64]struct{})
|
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if stat == nil {
|
|
return fmt.Errorf("cannot get underlying data for %s", path)
|
|
}
|
|
if _, ok := inos[stat.Ino]; ok {
|
|
// Inode already processed, skip
|
|
return nil
|
|
}
|
|
|
|
uid, gid, err := i.idMapping.ToContainer(int(stat.Uid), int(stat.Gid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inos[stat.Ino] = struct{}{}
|
|
|
|
return chownWithCaps(path, uid, gid)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func (i *ImageService) unremapRootFS(ctx context.Context, mounts []mount.Mount) error {
|
|
return mount.WithTempMount(ctx, mounts, func(root string) error {
|
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if stat == nil {
|
|
return fmt.Errorf("cannot get underlying data for %s", path)
|
|
}
|
|
|
|
uid, gid, err := i.idMapping.ToContainer(int(stat.Uid), int(stat.Gid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return chownWithCaps(path, uid, gid)
|
|
})
|
|
})
|
|
}
|
|
|
|
// chownWithCaps will chown path and preserve the extended attributes.
|
|
// chowning a file will remove the capabilities, so we need to first get all of
|
|
// them, chown the file, and then set the extended attributes
|
|
func chownWithCaps(path string, uid int, gid int) error {
|
|
xattrKeys, err := sysx.LListxattr(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
xattrs := make(map[string][]byte, len(xattrKeys))
|
|
|
|
for _, xattr := range xattrKeys {
|
|
data, err := sysx.LGetxattr(path, xattr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
xattrs[xattr] = data
|
|
}
|
|
|
|
if err := os.Lchown(path, uid, gid); err != nil {
|
|
return err
|
|
}
|
|
|
|
for xattrKey, xattrValue := range xattrs {
|
|
length := len(xattrValue)
|
|
// make sure the capabilities are version 2,
|
|
// capabilities version 3 also store the root uid of the namespace,
|
|
// we don't want this when we are in userns-remap mode
|
|
// see: https://github.com/moby/moby/pull/41724
|
|
if xattrKey == "security.capability" && xattrValue[versionOffset] == vfsCapRevision3 {
|
|
xattrValue[versionOffset] = vfsCapRevision2
|
|
length = xattrCapsSz2
|
|
}
|
|
if err := sysx.LSetxattr(path, xattrKey, xattrValue[:length], 0); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|