55 "errors"
66 "fmt"
77 "os"
8+ "path"
9+ "strconv"
810 "strings"
911 "sync"
1012
@@ -13,7 +15,11 @@ import (
1315)
1416
1517// OpenFile opens a cgroup file in a given dir with given flags.
16- // It is supposed to be used for cgroup files only.
18+ // It is supposed to be used for cgroup files only, and returns
19+ // an error if the file is not a cgroup file.
20+ //
21+ // Arguments dir and file are joined together to form an absolute path
22+ // to a file being opened.
1723func OpenFile (dir , file string , flags int ) (* os.File , error ) {
1824 if dir == "" {
1925 return nil , fmt .Errorf ("no directory specified for %s" , file )
@@ -109,43 +115,59 @@ func prepareOpenat2() error {
109115 return prepErr
110116}
111117
112- // OpenFile opens a cgroup file in a given dir with given flags.
113- // It is supposed to be used for cgroup files only.
114118func openFile (dir , file string , flags int ) (* os.File , error ) {
115119 mode := os .FileMode (0 )
116120 if TestMode && flags & os .O_WRONLY != 0 {
117121 // "emulate" cgroup fs for unit tests
118122 flags |= os .O_TRUNC | os .O_CREATE
119123 mode = 0o600
120124 }
125+ path := path .Join (dir , file )
121126 if prepareOpenat2 () != nil {
122- return openFallback (dir , file , flags , mode )
127+ return openFallback (path , flags , mode )
123128 }
124- reldir := strings .TrimPrefix (dir , cgroupfsPrefix )
125- if len (reldir ) == len (dir ) { // non-standard path, old system?
126- return openFallback (dir , file , flags , mode )
129+ relPath := strings .TrimPrefix (path , cgroupfsPrefix )
130+ if len (relPath ) == len (path ) { // non-standard path, old system?
131+ return openFallback (path , flags , mode )
127132 }
128133
129- relname := reldir + "/" + file
130- fd , err := unix .Openat2 (cgroupFd , relname ,
134+ fd , err := unix .Openat2 (cgroupFd , relPath ,
131135 & unix.OpenHow {
132136 Resolve : resolveFlags ,
133137 Flags : uint64 (flags ) | unix .O_CLOEXEC ,
134138 Mode : uint64 (mode ),
135139 })
136140 if err != nil {
137- return nil , & os.PathError {Op : "openat2" , Path : dir + "/" + file , Err : err }
141+ err = & os.PathError {Op : "openat2" , Path : path , Err : err }
142+ // Check if cgroupFd is still opened to cgroupfsDir
143+ // (happens when this package is incorrectly used
144+ // across the chroot/pivot_root/mntns boundary, or
145+ // when /sys/fs/cgroup is remounted).
146+ //
147+ // TODO: if such usage will ever be common, amend this
148+ // to reopen cgroupFd and retry openat2.
149+ fdStr := strconv .Itoa (cgroupFd )
150+ fdDest , _ := os .Readlink ("/proc/self/fd/" + fdStr )
151+ if fdDest != cgroupfsDir {
152+ // Wrap the error so it is clear that cgroupFd
153+ // is opened to an unexpected/wrong directory.
154+ err = fmt .Errorf ("cgroupFd %s unexpectedly opened to %s != %s: %w" ,
155+ fdStr , fdDest , cgroupfsDir , err )
156+ }
157+ return nil , err
138158 }
139159
140- return os .NewFile (uintptr (fd ), cgroupfsPrefix + relname ), nil
160+ return os .NewFile (uintptr (fd ), path ), nil
141161}
142162
143163var errNotCgroupfs = errors .New ("not a cgroup file" )
144164
145- // openFallback is used when openat2(2) is not available. It checks the opened
165+ // Can be changed by unit tests.
166+ var openFallback = openAndCheck
167+
168+ // openAndCheck is used when openat2(2) is not available. It checks the opened
146169// file is on cgroupfs, returning an error otherwise.
147- func openFallback (dir , file string , flags int , mode os.FileMode ) (* os.File , error ) {
148- path := dir + "/" + file
170+ func openAndCheck (path string , flags int , mode os.FileMode ) (* os.File , error ) {
149171 fd , err := os .OpenFile (path , flags , mode )
150172 if err != nil {
151173 return nil , err
0 commit comments