1package sysinfo
2
3import (
4	"fmt"
5	"io/ioutil"
6	"os"
7	"path"
8	"strings"
9
10	"github.com/opencontainers/runc/libcontainer/cgroups"
11	"github.com/sirupsen/logrus"
12	"golang.org/x/sys/unix"
13)
14
15func findCgroupMountpoints() (map[string]string, error) {
16	cgMounts, err := cgroups.GetCgroupMounts(false)
17	if err != nil {
18		return nil, fmt.Errorf("Failed to parse cgroup information: %v", err)
19	}
20	mps := make(map[string]string)
21	for _, m := range cgMounts {
22		for _, ss := range m.Subsystems {
23			mps[ss] = m.Mountpoint
24		}
25	}
26	return mps, nil
27}
28
29// New returns a new SysInfo, using the filesystem to detect which features
30// the kernel supports. If `quiet` is `false` warnings are printed in logs
31// whenever an error occurs or misconfigurations are present.
32func New(quiet bool) *SysInfo {
33	sysInfo := &SysInfo{}
34	cgMounts, err := findCgroupMountpoints()
35	if err != nil {
36		logrus.Warnf("Failed to parse cgroup information: %v", err)
37	} else {
38		sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet)
39		sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet)
40		sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet)
41		sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet)
42		sysInfo.cgroupPids = checkCgroupPids(quiet)
43	}
44
45	_, ok := cgMounts["devices"]
46	sysInfo.CgroupDevicesEnabled = ok
47
48	sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward")
49	sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables")
50	sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
51
52	// Check if AppArmor is supported.
53	if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
54		sysInfo.AppArmor = true
55	}
56
57	// Check if Seccomp is supported, via CONFIG_SECCOMP.
58	if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL {
59		// Make sure the kernel has CONFIG_SECCOMP_FILTER.
60		if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL {
61			sysInfo.Seccomp = true
62		}
63	}
64
65	return sysInfo
66}
67
68// checkCgroupMem reads the memory information from the memory cgroup mount point.
69func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo {
70	mountPoint, ok := cgMounts["memory"]
71	if !ok {
72		if !quiet {
73			logrus.Warn("Your kernel does not support cgroup memory limit")
74		}
75		return cgroupMemInfo{}
76	}
77
78	swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes")
79	if !quiet && !swapLimit {
80		logrus.Warn("Your kernel does not support swap memory limit")
81	}
82	memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes")
83	if !quiet && !memoryReservation {
84		logrus.Warn("Your kernel does not support memory reservation")
85	}
86	oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control")
87	if !quiet && !oomKillDisable {
88		logrus.Warn("Your kernel does not support oom control")
89	}
90	memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness")
91	if !quiet && !memorySwappiness {
92		logrus.Warn("Your kernel does not support memory swappiness")
93	}
94	kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes")
95	if !quiet && !kernelMemory {
96		logrus.Warn("Your kernel does not support kernel memory limit")
97	}
98
99	return cgroupMemInfo{
100		MemoryLimit:       true,
101		SwapLimit:         swapLimit,
102		MemoryReservation: memoryReservation,
103		OomKillDisable:    oomKillDisable,
104		MemorySwappiness:  memorySwappiness,
105		KernelMemory:      kernelMemory,
106	}
107}
108
109// checkCgroupCPU reads the cpu information from the cpu cgroup mount point.
110func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo {
111	mountPoint, ok := cgMounts["cpu"]
112	if !ok {
113		if !quiet {
114			logrus.Warn("Unable to find cpu cgroup in mounts")
115		}
116		return cgroupCPUInfo{}
117	}
118
119	cpuShares := cgroupEnabled(mountPoint, "cpu.shares")
120	if !quiet && !cpuShares {
121		logrus.Warn("Your kernel does not support cgroup cpu shares")
122	}
123
124	cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us")
125	if !quiet && !cpuCfsPeriod {
126		logrus.Warn("Your kernel does not support cgroup cfs period")
127	}
128
129	cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us")
130	if !quiet && !cpuCfsQuota {
131		logrus.Warn("Your kernel does not support cgroup cfs quotas")
132	}
133
134	cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us")
135	if !quiet && !cpuRealtimePeriod {
136		logrus.Warn("Your kernel does not support cgroup rt period")
137	}
138
139	cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us")
140	if !quiet && !cpuRealtimeRuntime {
141		logrus.Warn("Your kernel does not support cgroup rt runtime")
142	}
143
144	return cgroupCPUInfo{
145		CPUShares:          cpuShares,
146		CPUCfsPeriod:       cpuCfsPeriod,
147		CPUCfsQuota:        cpuCfsQuota,
148		CPURealtimePeriod:  cpuRealtimePeriod,
149		CPURealtimeRuntime: cpuRealtimeRuntime,
150	}
151}
152
153// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point.
154func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo {
155	mountPoint, ok := cgMounts["blkio"]
156	if !ok {
157		if !quiet {
158			logrus.Warn("Unable to find blkio cgroup in mounts")
159		}
160		return cgroupBlkioInfo{}
161	}
162
163	weight := cgroupEnabled(mountPoint, "blkio.weight")
164	if !quiet && !weight {
165		logrus.Warn("Your kernel does not support cgroup blkio weight")
166	}
167
168	weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device")
169	if !quiet && !weightDevice {
170		logrus.Warn("Your kernel does not support cgroup blkio weight_device")
171	}
172
173	readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
174	if !quiet && !readBpsDevice {
175		logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device")
176	}
177
178	writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
179	if !quiet && !writeBpsDevice {
180		logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device")
181	}
182	readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device")
183	if !quiet && !readIOpsDevice {
184		logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device")
185	}
186
187	writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device")
188	if !quiet && !writeIOpsDevice {
189		logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device")
190	}
191	return cgroupBlkioInfo{
192		BlkioWeight:          weight,
193		BlkioWeightDevice:    weightDevice,
194		BlkioReadBpsDevice:   readBpsDevice,
195		BlkioWriteBpsDevice:  writeBpsDevice,
196		BlkioReadIOpsDevice:  readIOpsDevice,
197		BlkioWriteIOpsDevice: writeIOpsDevice,
198	}
199}
200
201// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point.
202func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo {
203	mountPoint, ok := cgMounts["cpuset"]
204	if !ok {
205		if !quiet {
206			logrus.Warn("Unable to find cpuset cgroup in mounts")
207		}
208		return cgroupCpusetInfo{}
209	}
210
211	cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus"))
212	if err != nil {
213		return cgroupCpusetInfo{}
214	}
215
216	mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems"))
217	if err != nil {
218		return cgroupCpusetInfo{}
219	}
220
221	return cgroupCpusetInfo{
222		Cpuset: true,
223		Cpus:   strings.TrimSpace(string(cpus)),
224		Mems:   strings.TrimSpace(string(mems)),
225	}
226}
227
228// checkCgroupPids reads the pids information from the pids cgroup mount point.
229func checkCgroupPids(quiet bool) cgroupPids {
230	_, err := cgroups.FindCgroupMountpoint("pids")
231	if err != nil {
232		if !quiet {
233			logrus.Warn(err)
234		}
235		return cgroupPids{}
236	}
237
238	return cgroupPids{
239		PidsLimit: true,
240	}
241}
242
243func cgroupEnabled(mountPoint, name string) bool {
244	_, err := os.Stat(path.Join(mountPoint, name))
245	return err == nil
246}
247
248func readProcBool(path string) bool {
249	val, err := ioutil.ReadFile(path)
250	if err != nil {
251		return false
252	}
253	return strings.TrimSpace(string(val)) == "1"
254}
255