skopeo/vendor/github.com/containers/storage/drivers/chown_unix.go
renovate[bot] fa1762f52b fix(deps): update module github.com/containers/image/v5 to v5.33.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2024-11-12 20:34:31 +01:00

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
}