File: chown_unix.go

package info (click to toggle)
golang-github-containers-storage 1.59.1%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 4,184 kB
  • sloc: sh: 630; ansic: 389; makefile: 143; awk: 12
file content (126 lines) | stat: -rw-r--r-- 3,344 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//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), //nolint:unconvert
		Ino: 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.ENOTSUP) && !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
}