1 package cgroups
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "os"
8 "path"
9 "strconv"
10 "strings"
11 "sync"
12
13 "github.com/opencontainers/runc/libcontainer/utils"
14 "github.com/sirupsen/logrus"
15 "golang.org/x/sys/unix"
16 )
17
18
19
20
21
22
23
24 func OpenFile(dir, file string, flags int) (*os.File, error) {
25 if dir == "" {
26 return nil, fmt.Errorf("no directory specified for %s", file)
27 }
28 return openFile(dir, file, flags)
29 }
30
31
32
33 func ReadFile(dir, file string) (string, error) {
34 fd, err := OpenFile(dir, file, unix.O_RDONLY)
35 if err != nil {
36 return "", err
37 }
38 defer fd.Close()
39 var buf bytes.Buffer
40
41 _, err = buf.ReadFrom(fd)
42 return buf.String(), err
43 }
44
45
46
47 func WriteFile(dir, file, data string) error {
48 fd, err := OpenFile(dir, file, unix.O_WRONLY)
49 if err != nil {
50 return err
51 }
52 defer fd.Close()
53 if err := retryingWriteFile(fd, data); err != nil {
54
55 return fmt.Errorf("failed to write %q: %w", data, err)
56 }
57 return nil
58 }
59
60 func retryingWriteFile(fd *os.File, data string) error {
61 for {
62 _, err := fd.Write([]byte(data))
63 if errors.Is(err, unix.EINTR) {
64 logrus.Infof("interrupted while writing %s to %s", data, fd.Name())
65 continue
66 }
67 return err
68 }
69 }
70
71 const (
72 cgroupfsDir = "/sys/fs/cgroup"
73 cgroupfsPrefix = cgroupfsDir + "/"
74 )
75
76 var (
77
78 TestMode bool
79
80 cgroupRootHandle *os.File
81 prepOnce sync.Once
82 prepErr error
83 resolveFlags uint64
84 )
85
86 func prepareOpenat2() error {
87 prepOnce.Do(func() {
88 fd, err := unix.Openat2(-1, cgroupfsDir, &unix.OpenHow{
89 Flags: unix.O_DIRECTORY | unix.O_PATH | unix.O_CLOEXEC,
90 })
91 if err != nil {
92 prepErr = &os.PathError{Op: "openat2", Path: cgroupfsDir, Err: err}
93 if err != unix.ENOSYS {
94 logrus.Warnf("falling back to securejoin: %s", prepErr)
95 } else {
96 logrus.Debug("openat2 not available, falling back to securejoin")
97 }
98 return
99 }
100 file := os.NewFile(uintptr(fd), cgroupfsDir)
101
102 var st unix.Statfs_t
103 if err := unix.Fstatfs(int(file.Fd()), &st); err != nil {
104 prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err}
105 logrus.Warnf("falling back to securejoin: %s", prepErr)
106 return
107 }
108
109 cgroupRootHandle = file
110 resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS
111 if st.Type == unix.CGROUP2_SUPER_MAGIC {
112
113 resolveFlags |= unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS
114 }
115 })
116
117 return prepErr
118 }
119
120 func openFile(dir, file string, flags int) (*os.File, error) {
121 mode := os.FileMode(0)
122 if TestMode && flags&os.O_WRONLY != 0 {
123
124 flags |= os.O_TRUNC | os.O_CREATE
125 mode = 0o600
126 }
127 path := path.Join(dir, utils.CleanPath(file))
128 if prepareOpenat2() != nil {
129 return openFallback(path, flags, mode)
130 }
131 relPath := strings.TrimPrefix(path, cgroupfsPrefix)
132 if len(relPath) == len(path) {
133 return openFallback(path, flags, mode)
134 }
135
136 fd, err := unix.Openat2(int(cgroupRootHandle.Fd()), relPath,
137 &unix.OpenHow{
138 Resolve: resolveFlags,
139 Flags: uint64(flags) | unix.O_CLOEXEC,
140 Mode: uint64(mode),
141 })
142 if err != nil {
143 err = &os.PathError{Op: "openat2", Path: path, Err: err}
144
145
146
147
148
149
150
151 fdStr := strconv.Itoa(int(cgroupRootHandle.Fd()))
152 fdDest, _ := os.Readlink("/proc/self/fd/" + fdStr)
153 if fdDest != cgroupfsDir {
154
155
156 err = fmt.Errorf("cgroupRootHandle %d unexpectedly opened to %s != %s: %w",
157 cgroupRootHandle.Fd(), fdDest, cgroupfsDir, err)
158 }
159 return nil, err
160 }
161
162 return os.NewFile(uintptr(fd), path), nil
163 }
164
165 var errNotCgroupfs = errors.New("not a cgroup file")
166
167
168 var openFallback = openAndCheck
169
170
171
172 func openAndCheck(path string, flags int, mode os.FileMode) (*os.File, error) {
173 fd, err := os.OpenFile(path, flags, mode)
174 if err != nil {
175 return nil, err
176 }
177 if TestMode {
178 return fd, nil
179 }
180
181 var st unix.Statfs_t
182 if err := unix.Fstatfs(int(fd.Fd()), &st); err != nil {
183 _ = fd.Close()
184 return nil, &os.PathError{Op: "statfs", Path: path, Err: err}
185 }
186 if st.Type != unix.CGROUP_SUPER_MAGIC && st.Type != unix.CGROUP2_SUPER_MAGIC {
187 _ = fd.Close()
188 return nil, &os.PathError{Op: "open", Path: path, Err: errNotCgroupfs}
189 }
190
191 return fd, nil
192 }
193
View as plain text