mirror of
https://github.com/containers/skopeo.git
synced 2025-06-05 04:52:07 +00:00
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Signed-off-by: Miloslav Trmač <mitr@redhat.com>
127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
//go:build !windows && !darwin
|
|
|
|
package graphdriver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/containers/storage/pkg/idtools"
|
|
"github.com/containers/storage/pkg/system"
|
|
)
|
|
|
|
type inode struct {
|
|
Dev uint64
|
|
Ino uint64
|
|
}
|
|
|
|
type platformChowner struct {
|
|
mutex sync.Mutex
|
|
inodes map[inode]string
|
|
}
|
|
|
|
func newLChowner() *platformChowner {
|
|
return &platformChowner{
|
|
inodes: make(map[inode]string),
|
|
}
|
|
}
|
|
|
|
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
|
st, ok := info.Sys().(*syscall.Stat_t)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
i := inode{
|
|
Dev: uint64(st.Dev),
|
|
Ino: uint64(st.Ino),
|
|
}
|
|
|
|
c.mutex.Lock()
|
|
|
|
oldTarget, found := c.inodes[i]
|
|
if !found {
|
|
c.inodes[i] = path
|
|
}
|
|
|
|
// If we are dealing with a file with multiple links then keep the lock until the file is
|
|
// chowned to avoid a race where we link to the old version if the file is copied up.
|
|
if found || st.Nlink > 1 {
|
|
defer c.mutex.Unlock()
|
|
} else {
|
|
c.mutex.Unlock()
|
|
}
|
|
|
|
if found {
|
|
// If the dev/inode was already chowned then create a link to the old target instead
|
|
// of chowning it again. This is necessary when the underlying file system breaks
|
|
// inodes on copy-up (as it is with overlay with index=off) to maintain the original
|
|
// link and correct file ownership.
|
|
|
|
// The target already exists so remove it before creating the link to the new target.
|
|
if err := os.Remove(path); err != nil {
|
|
return err
|
|
}
|
|
return os.Link(oldTarget, path)
|
|
}
|
|
|
|
// Map an on-disk UID/GID pair from host to container
|
|
// using the first map, then back to the host using the
|
|
// second map. Skip that first step if they're 0, to
|
|
// compensate for cases where a parent layer should
|
|
// have had a mapped value, but didn't.
|
|
uid, gid := int(st.Uid), int(st.Gid)
|
|
if toContainer != nil {
|
|
pair := idtools.IDPair{
|
|
UID: uid,
|
|
GID: gid,
|
|
}
|
|
mappedUID, mappedGID, err := toContainer.ToContainer(pair)
|
|
if err != nil {
|
|
if (uid != 0) || (gid != 0) {
|
|
return fmt.Errorf("mapping host ID pair %#v for %q to container: %w", pair, path, err)
|
|
}
|
|
mappedUID, mappedGID = uid, gid
|
|
}
|
|
uid, gid = mappedUID, mappedGID
|
|
}
|
|
if toHost != nil {
|
|
pair := idtools.IDPair{
|
|
UID: uid,
|
|
GID: gid,
|
|
}
|
|
mappedPair, err := toHost.ToHostOverflow(pair)
|
|
if err != nil {
|
|
return fmt.Errorf("mapping container ID pair %#v for %q to host: %w", pair, path, err)
|
|
}
|
|
uid, gid = mappedPair.UID, mappedPair.GID
|
|
}
|
|
if uid != int(st.Uid) || gid != int(st.Gid) {
|
|
cap, err := system.Lgetxattr(path, "security.capability")
|
|
if err != nil && !errors.Is(err, system.EOPNOTSUPP) && !errors.Is(err, system.EOVERFLOW) && err != system.ErrNotSupportedPlatform {
|
|
return fmt.Errorf("%s: %w", os.Args[0], err)
|
|
}
|
|
|
|
// Make the change.
|
|
if err := system.Lchown(path, uid, gid); err != nil {
|
|
return fmt.Errorf("%s: %w", os.Args[0], err)
|
|
}
|
|
// Restore the SUID and SGID bits if they were originally set.
|
|
if (info.Mode()&os.ModeSymlink == 0) && info.Mode()&(os.ModeSetuid|os.ModeSetgid) != 0 {
|
|
if err := system.Chmod(path, info.Mode()); err != nil {
|
|
return fmt.Errorf("%s: %w", os.Args[0], err)
|
|
}
|
|
}
|
|
if cap != nil {
|
|
if err := system.Lsetxattr(path, "security.capability", cap, 0); err != nil {
|
|
return fmt.Errorf("%s: %w", os.Args[0], err)
|
|
}
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|