v1_utils.go 6.4 KB

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