v1_utils.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package cgroups
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "sync"
  9. "syscall"
  10. securejoin "github.com/cyphar/filepath-securejoin"
  11. "github.com/moby/sys/mountinfo"
  12. "golang.org/x/sys/unix"
  13. )
  14. // Code in this source file are specific to cgroup v1,
  15. // and must not be used from any cgroup v2 code.
  16. const (
  17. CgroupNamePrefix = "name="
  18. defaultPrefix = "/sys/fs/cgroup"
  19. )
  20. var (
  21. errUnified = errors.New("not implemented for cgroup v2 unified hierarchy")
  22. ErrV1NoUnified = errors.New("invalid configuration: cannot use unified on cgroup v1")
  23. readMountinfoOnce sync.Once
  24. readMountinfoErr error
  25. cgroupMountinfo []*mountinfo.Info
  26. )
  27. type NotFoundError struct {
  28. Subsystem string
  29. }
  30. func (e *NotFoundError) Error() string {
  31. return fmt.Sprintf("mountpoint for %s not found", e.Subsystem)
  32. }
  33. func NewNotFoundError(sub string) error {
  34. return &NotFoundError{
  35. Subsystem: sub,
  36. }
  37. }
  38. func IsNotFound(err error) bool {
  39. var nfErr *NotFoundError
  40. return errors.As(err, &nfErr)
  41. }
  42. func tryDefaultPath(cgroupPath, subsystem string) string {
  43. if !strings.HasPrefix(defaultPrefix, cgroupPath) {
  44. return ""
  45. }
  46. // remove possible prefix
  47. subsystem = strings.TrimPrefix(subsystem, CgroupNamePrefix)
  48. // Make sure we're still under defaultPrefix, and resolve
  49. // a possible symlink (like cpu -> cpu,cpuacct).
  50. path, err := securejoin.SecureJoin(defaultPrefix, subsystem)
  51. if err != nil {
  52. return ""
  53. }
  54. // (1) path should be a directory.
  55. st, err := os.Lstat(path)
  56. if err != nil || !st.IsDir() {
  57. return ""
  58. }
  59. // (2) path should be a mount point.
  60. pst, err := os.Lstat(filepath.Dir(path))
  61. if err != nil {
  62. return ""
  63. }
  64. if st.Sys().(*syscall.Stat_t).Dev == pst.Sys().(*syscall.Stat_t).Dev {
  65. // parent dir has the same dev -- path is not a mount point
  66. return ""
  67. }
  68. // (3) path should have 'cgroup' fs type.
  69. fst := unix.Statfs_t{}
  70. err = unix.Statfs(path, &fst)
  71. if err != nil || fst.Type != unix.CGROUP_SUPER_MAGIC {
  72. return ""
  73. }
  74. return path
  75. }
  76. // readCgroupMountinfo returns a list of cgroup v1 mounts (i.e. the ones
  77. // with fstype of "cgroup") for the current running process.
  78. //
  79. // The results are cached (to avoid re-reading mountinfo which is relatively
  80. // expensive), so it is assumed that cgroup mounts are not being changed.
  81. func readCgroupMountinfo() ([]*mountinfo.Info, error) {
  82. readMountinfoOnce.Do(func() {
  83. cgroupMountinfo, readMountinfoErr = mountinfo.GetMounts(
  84. mountinfo.FSTypeFilter("cgroup"),
  85. )
  86. })
  87. return cgroupMountinfo, readMountinfoErr
  88. }
  89. // https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
  90. func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) {
  91. if IsCgroup2UnifiedMode() {
  92. return "", errUnified
  93. }
  94. // If subsystem is empty, we look for the cgroupv2 hybrid path.
  95. if len(subsystem) == 0 {
  96. return hybridMountpoint, nil
  97. }
  98. // Avoid parsing mountinfo by trying the default path first, if possible.
  99. if path := tryDefaultPath(cgroupPath, subsystem); path != "" {
  100. return path, nil
  101. }
  102. mnt, _, err := FindCgroupMountpointAndRoot(cgroupPath, subsystem)
  103. return mnt, err
  104. }
  105. func FindCgroupMountpointAndRoot(cgroupPath, subsystem string) (string, string, error) {
  106. if IsCgroup2UnifiedMode() {
  107. return "", "", errUnified
  108. }
  109. mi, err := readCgroupMountinfo()
  110. if err != nil {
  111. return "", "", err
  112. }
  113. return findCgroupMountpointAndRootFromMI(mi, cgroupPath, subsystem)
  114. }
  115. func findCgroupMountpointAndRootFromMI(mounts []*mountinfo.Info, cgroupPath, subsystem string) (string, string, error) {
  116. for _, mi := range mounts {
  117. if strings.HasPrefix(mi.Mountpoint, cgroupPath) {
  118. for _, opt := range strings.Split(mi.VFSOptions, ",") {
  119. if opt == subsystem {
  120. return mi.Mountpoint, mi.Root, nil
  121. }
  122. }
  123. }
  124. }
  125. return "", "", NewNotFoundError(subsystem)
  126. }
  127. func (m Mount) GetOwnCgroup(cgroups map[string]string) (string, error) {
  128. if len(m.Subsystems) == 0 {
  129. return "", errors.New("no subsystem for mount")
  130. }
  131. return getControllerPath(m.Subsystems[0], cgroups)
  132. }
  133. func getCgroupMountsHelper(ss map[string]bool, mounts []*mountinfo.Info, all bool) ([]Mount, error) {
  134. res := make([]Mount, 0, len(ss))
  135. numFound := 0
  136. for _, mi := range mounts {
  137. m := Mount{
  138. Mountpoint: mi.Mountpoint,
  139. Root: mi.Root,
  140. }
  141. for _, opt := range strings.Split(mi.VFSOptions, ",") {
  142. seen, known := ss[opt]
  143. if !known || (!all && seen) {
  144. continue
  145. }
  146. ss[opt] = true
  147. opt = strings.TrimPrefix(opt, CgroupNamePrefix)
  148. m.Subsystems = append(m.Subsystems, opt)
  149. numFound++
  150. }
  151. if len(m.Subsystems) > 0 || all {
  152. res = append(res, m)
  153. }
  154. if !all && numFound >= len(ss) {
  155. break
  156. }
  157. }
  158. return res, nil
  159. }
  160. func getCgroupMountsV1(all bool) ([]Mount, error) {
  161. mi, err := readCgroupMountinfo()
  162. if err != nil {
  163. return nil, err
  164. }
  165. allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
  166. if err != nil {
  167. return nil, err
  168. }
  169. allMap := make(map[string]bool)
  170. for s := range allSubsystems {
  171. allMap[s] = false
  172. }
  173. return getCgroupMountsHelper(allMap, mi, all)
  174. }
  175. // GetOwnCgroup returns the relative path to the cgroup docker is running in.
  176. func GetOwnCgroup(subsystem string) (string, error) {
  177. if IsCgroup2UnifiedMode() {
  178. return "", errUnified
  179. }
  180. cgroups, err := ParseCgroupFile("/proc/self/cgroup")
  181. if err != nil {
  182. return "", err
  183. }
  184. return getControllerPath(subsystem, cgroups)
  185. }
  186. func GetOwnCgroupPath(subsystem string) (string, error) {
  187. cgroup, err := GetOwnCgroup(subsystem)
  188. if err != nil {
  189. return "", err
  190. }
  191. // If subsystem is empty, we look for the cgroupv2 hybrid path.
  192. if len(subsystem) == 0 {
  193. return hybridMountpoint, nil
  194. }
  195. return getCgroupPathHelper(subsystem, cgroup)
  196. }
  197. func GetInitCgroup(subsystem string) (string, error) {
  198. if IsCgroup2UnifiedMode() {
  199. return "", errUnified
  200. }
  201. cgroups, err := ParseCgroupFile("/proc/1/cgroup")
  202. if err != nil {
  203. return "", err
  204. }
  205. return getControllerPath(subsystem, cgroups)
  206. }
  207. func GetInitCgroupPath(subsystem string) (string, error) {
  208. cgroup, err := GetInitCgroup(subsystem)
  209. if err != nil {
  210. return "", err
  211. }
  212. return getCgroupPathHelper(subsystem, cgroup)
  213. }
  214. func getCgroupPathHelper(subsystem, cgroup string) (string, error) {
  215. mnt, root, err := FindCgroupMountpointAndRoot("", subsystem)
  216. if err != nil {
  217. return "", err
  218. }
  219. // This is needed for nested containers, because in /proc/self/cgroup we
  220. // see paths from host, which don't exist in container.
  221. relCgroup, err := filepath.Rel(root, cgroup)
  222. if err != nil {
  223. return "", err
  224. }
  225. return filepath.Join(mnt, relCgroup), nil
  226. }
  227. func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {
  228. if IsCgroup2UnifiedMode() {
  229. return "", errUnified
  230. }
  231. if p, ok := cgroups[subsystem]; ok {
  232. return p, nil
  233. }
  234. if p, ok := cgroups[CgroupNamePrefix+subsystem]; ok {
  235. return p, nil
  236. }
  237. return "", NewNotFoundError(subsystem)
  238. }