//go:build !windows && !darwin // +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 }