1// Package capabilities is used for managing sets of linux capabilities.
2package capabilities
3
4import (
5	"sort"
6	"strings"
7)
8
9type nothing struct{}
10
11var null = nothing{}
12
13// Set represents a group linux capabilities, implementing some useful set
14// operations, taking care of name normalization, and sentinel value expansions.
15//
16// Linux capabilities can be expressed in multiple ways when working with docker
17// and/or executor, along with Nomad configuration.
18//
19// Capability names may be upper or lower case, and may or may not be prefixed
20// with "CAP_" or "cap_". On top of that, Nomad interprets the special name "all"
21// and "ALL" to mean "all capabilities supported by the operating system".
22type Set struct {
23	data map[string]nothing
24}
25
26// New creates a new Set setting caps as the initial elements.
27func New(caps []string) *Set {
28	m := make(map[string]nothing, len(caps))
29	for _, c := range caps {
30		insert(m, c)
31	}
32	return &Set{data: m}
33}
34
35// Add cap into s.
36func (s *Set) Add(cap string) {
37	insert(s.data, cap)
38}
39
40func insert(data map[string]nothing, cap string) {
41	switch name := normalize(cap); name {
42	case "":
43	case "all":
44		for k, v := range Supported().data {
45			data[k] = v
46		}
47		return
48	default:
49		data[name] = null
50	}
51}
52
53// Remove caps from s.
54func (s *Set) Remove(caps []string) {
55	for _, c := range caps {
56		name := normalize(c)
57		if name == "all" {
58			s.data = make(map[string]nothing)
59			return
60		}
61		delete(s.data, name)
62	}
63}
64
65// Union returns of Set of elements of both s and b.
66func (s *Set) Union(b *Set) *Set {
67	data := make(map[string]nothing)
68	for c := range s.data {
69		data[c] = null
70	}
71	for c := range b.data {
72		data[c] = null
73	}
74	return &Set{data: data}
75}
76
77// Difference returns the Set of elements of b not in s.
78func (s *Set) Difference(b *Set) *Set {
79	data := make(map[string]nothing)
80	for c := range b.data {
81		if _, exists := s.data[c]; !exists {
82			data[c] = null
83		}
84	}
85	return &Set{data: data}
86}
87
88// Intersect returns the Set of elements in both s and b.
89func (s *Set) Intersect(b *Set) *Set {
90	data := make(map[string]nothing)
91	for c := range s.data {
92		if _, exists := b.data[c]; exists {
93			data[c] = null
94		}
95	}
96	return &Set{data: data}
97}
98
99// Empty return true if no capabilities exist in s.
100func (s *Set) Empty() bool {
101	return len(s.data) == 0
102}
103
104// String returns the normalized and sorted string representation of s.
105func (s *Set) String() string {
106	return strings.Join(s.Slice(false), ", ")
107}
108
109// Slice returns a sorted slice of capabilities in s.
110//
111// upper - indicates whether to uppercase and prefix capabilities with CAP_
112func (s *Set) Slice(upper bool) []string {
113	caps := make([]string, 0, len(s.data))
114	for c := range s.data {
115		if upper {
116			c = "CAP_" + strings.ToUpper(c)
117		}
118		caps = append(caps, c)
119	}
120	sort.Strings(caps)
121	return caps
122}
123
124// linux capabilities are often named in 4 possible ways - upper or lower case,
125// and with or without a CAP_ prefix
126//
127// since we must do comparisons on cap names, always normalize the names before
128// letting them into the Set data-structure
129func normalize(name string) string {
130	spaces := strings.TrimSpace(name)
131	lower := strings.ToLower(spaces)
132	trim := strings.TrimPrefix(lower, "cap_")
133	return trim
134}
135