| package fs |
| |
| import ( |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strconv" |
| "sync" |
| |
| "github.com/docker/libcontainer/cgroups" |
| "github.com/docker/libcontainer/configs" |
| ) |
| |
| var ( |
| subsystems = map[string]subsystem{ |
| "devices": &DevicesGroup{}, |
| "memory": &MemoryGroup{}, |
| "cpu": &CpuGroup{}, |
| "cpuset": &CpusetGroup{}, |
| "cpuacct": &CpuacctGroup{}, |
| "blkio": &BlkioGroup{}, |
| "perf_event": &PerfEventGroup{}, |
| "freezer": &FreezerGroup{}, |
| } |
| CgroupProcesses = "cgroup.procs" |
| ) |
| |
| type subsystem interface { |
| // Returns the stats, as 'stats', corresponding to the cgroup under 'path'. |
| GetStats(path string, stats *cgroups.Stats) error |
| // Removes the cgroup represented by 'data'. |
| Remove(*data) error |
| // Creates and joins the cgroup represented by data. |
| Apply(*data) error |
| // Set the cgroup represented by cgroup. |
| Set(path string, cgroup *configs.Cgroup) error |
| } |
| |
| type Manager struct { |
| Cgroups *configs.Cgroup |
| Paths map[string]string |
| } |
| |
| // The absolute path to the root of the cgroup hierarchies. |
| var cgroupRootLock sync.Mutex |
| var cgroupRoot string |
| |
| // Gets the cgroupRoot. |
| func getCgroupRoot() (string, error) { |
| cgroupRootLock.Lock() |
| defer cgroupRootLock.Unlock() |
| |
| if cgroupRoot != "" { |
| return cgroupRoot, nil |
| } |
| |
| root, err := cgroups.FindCgroupMountpointDir() |
| if err != nil { |
| return "", err |
| } |
| |
| if _, err := os.Stat(root); err != nil { |
| return "", err |
| } |
| |
| cgroupRoot = root |
| return cgroupRoot, nil |
| } |
| |
| type data struct { |
| root string |
| cgroup string |
| c *configs.Cgroup |
| pid int |
| } |
| |
| func (m *Manager) Apply(pid int) error { |
| if m.Cgroups == nil { |
| return nil |
| } |
| |
| d, err := getCgroupData(m.Cgroups, pid) |
| if err != nil { |
| return err |
| } |
| |
| paths := make(map[string]string) |
| defer func() { |
| if err != nil { |
| cgroups.RemovePaths(paths) |
| } |
| }() |
| for name, sys := range subsystems { |
| if err := sys.Apply(d); err != nil { |
| return err |
| } |
| // TODO: Apply should, ideally, be reentrant or be broken up into a separate |
| // create and join phase so that the cgroup hierarchy for a container can be |
| // created then join consists of writing the process pids to cgroup.procs |
| p, err := d.path(name) |
| if err != nil { |
| if cgroups.IsNotFound(err) { |
| continue |
| } |
| return err |
| } |
| paths[name] = p |
| } |
| m.Paths = paths |
| |
| return nil |
| } |
| |
| func (m *Manager) Destroy() error { |
| return cgroups.RemovePaths(m.Paths) |
| } |
| |
| func (m *Manager) GetPaths() map[string]string { |
| return m.Paths |
| } |
| |
| // Symmetrical public function to update device based cgroups. Also available |
| // in the systemd implementation. |
| func ApplyDevices(c *configs.Cgroup, pid int) error { |
| d, err := getCgroupData(c, pid) |
| if err != nil { |
| return err |
| } |
| |
| devices := subsystems["devices"] |
| |
| return devices.Apply(d) |
| } |
| |
| func (m *Manager) GetStats() (*cgroups.Stats, error) { |
| stats := cgroups.NewStats() |
| for name, path := range m.Paths { |
| sys, ok := subsystems[name] |
| if !ok || !cgroups.PathExists(path) { |
| continue |
| } |
| if err := sys.GetStats(path, stats); err != nil { |
| return nil, err |
| } |
| } |
| |
| return stats, nil |
| } |
| |
| func (m *Manager) Set(container *configs.Config) error { |
| for name, path := range m.Paths { |
| sys, ok := subsystems[name] |
| if !ok || !cgroups.PathExists(path) { |
| continue |
| } |
| if err := sys.Set(path, container.Cgroups); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // Freeze toggles the container's freezer cgroup depending on the state |
| // provided |
| func (m *Manager) Freeze(state configs.FreezerState) error { |
| d, err := getCgroupData(m.Cgroups, 0) |
| if err != nil { |
| return err |
| } |
| |
| dir, err := d.path("freezer") |
| if err != nil { |
| return err |
| } |
| |
| prevState := m.Cgroups.Freezer |
| m.Cgroups.Freezer = state |
| |
| freezer := subsystems["freezer"] |
| err = freezer.Set(dir, m.Cgroups) |
| if err != nil { |
| m.Cgroups.Freezer = prevState |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (m *Manager) GetPids() ([]int, error) { |
| d, err := getCgroupData(m.Cgroups, 0) |
| if err != nil { |
| return nil, err |
| } |
| |
| dir, err := d.path("devices") |
| if err != nil { |
| return nil, err |
| } |
| |
| return cgroups.ReadProcsFile(dir) |
| } |
| |
| func getCgroupData(c *configs.Cgroup, pid int) (*data, error) { |
| root, err := getCgroupRoot() |
| if err != nil { |
| return nil, err |
| } |
| |
| cgroup := c.Name |
| if c.Parent != "" { |
| cgroup = filepath.Join(c.Parent, cgroup) |
| } |
| |
| return &data{ |
| root: root, |
| cgroup: cgroup, |
| c: c, |
| pid: pid, |
| }, nil |
| } |
| |
| func (raw *data) parent(subsystem string) (string, error) { |
| initPath, err := cgroups.GetInitCgroupDir(subsystem) |
| if err != nil { |
| return "", err |
| } |
| return filepath.Join(raw.root, subsystem, initPath), nil |
| } |
| |
| func (raw *data) path(subsystem string) (string, error) { |
| _, err := cgroups.FindCgroupMountpoint(subsystem) |
| // If we didn't mount the subsystem, there is no point we make the path. |
| if err != nil { |
| return "", err |
| } |
| |
| // If the cgroup name/path is absolute do not look relative to the cgroup of the init process. |
| if filepath.IsAbs(raw.cgroup) { |
| return filepath.Join(raw.root, subsystem, raw.cgroup), nil |
| } |
| |
| parent, err := raw.parent(subsystem) |
| if err != nil { |
| return "", err |
| } |
| |
| return filepath.Join(parent, raw.cgroup), nil |
| } |
| |
| func (raw *data) join(subsystem string) (string, error) { |
| path, err := raw.path(subsystem) |
| if err != nil { |
| return "", err |
| } |
| if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { |
| return "", err |
| } |
| if err := writeFile(path, CgroupProcesses, strconv.Itoa(raw.pid)); err != nil { |
| return "", err |
| } |
| return path, nil |
| } |
| |
| func writeFile(dir, file, data string) error { |
| return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) |
| } |
| |
| func readFile(dir, file string) (string, error) { |
| data, err := ioutil.ReadFile(filepath.Join(dir, file)) |
| return string(data), err |
| } |
| |
| func removePath(p string, err error) error { |
| if err != nil { |
| return err |
| } |
| if p != "" { |
| return os.RemoveAll(p) |
| } |
| return nil |
| } |