full diff: https://github.com/opencontainers/selinux/compare/v1.5.2...v1.6.0
This also adds a new dependency: github.com/willf/bitset
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
... | ... |
@@ -169,7 +169,8 @@ github.com/morikuni/aec 39771216ff4c63d11f5e604076f9 |
169 | 169 |
# metrics |
170 | 170 |
github.com/docker/go-metrics b619b3592b65de4f087d9f16863a7e6ff905973c # v0.0.1 |
171 | 171 |
|
172 |
-github.com/opencontainers/selinux c688bba66d7ecb448819836b96f9c416da8b0746 # v1.5.2 |
|
172 |
+github.com/opencontainers/selinux 25504e34a9826d481f6e2903963ecaa881749124 # v1.6.0 |
|
173 |
+github.com/willf/bitset 559910e8471e48d76d9e5a1ba15842dee77ad45d # v1.1.11 |
|
173 | 174 |
|
174 | 175 |
|
175 | 176 |
# archive/tar |
... | ... |
@@ -4,7 +4,11 @@ |
4 | 4 |
|
5 | 5 |
Common SELinux package used across the container ecosystem. |
6 | 6 |
|
7 |
-Please see the [godoc](https://godoc.org/github.com/opencontainers/selinux) for more information. |
|
7 |
+## Usage |
|
8 |
+ |
|
9 |
+When compiling consumers of this project, the `selinux` build tag must be used to enable selinux functionality. |
|
10 |
+ |
|
11 |
+For complete documentation, see [godoc](https://godoc.org/github.com/opencontainers/selinux). |
|
8 | 12 |
|
9 | 13 |
## Code of Conduct |
10 | 14 |
|
11 | 15 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,21 @@ |
0 |
+/* |
|
1 |
+Package selinux provides a high-level interface for interacting with selinux. |
|
2 |
+ |
|
3 |
+This package uses a selinux build tag to enable the selinux functionality. This |
|
4 |
+allows non-linux and linux users who do not have selinux support to still use |
|
5 |
+tools that rely on this library. |
|
6 |
+ |
|
7 |
+To compile with full selinux support use the -tags=selinux option in your build |
|
8 |
+and test commands. |
|
9 |
+ |
|
10 |
+Usage: |
|
11 |
+ |
|
12 |
+ import "github.com/opencontainers/selinux/go-selinux" |
|
13 |
+ |
|
14 |
+ // Ensure that selinux is enforcing mode. |
|
15 |
+ if selinux.EnforceMode() != selinux.Enforcing { |
|
16 |
+ selinux.SetEnforceMode(selinux.Enforcing) |
|
17 |
+ } |
|
18 |
+ |
|
19 |
+*/ |
|
20 |
+package selinux |
... | ... |
@@ -73,9 +73,9 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) { |
73 | 73 |
selinux.ReleaseLabel(processLabel) |
74 | 74 |
} |
75 | 75 |
processLabel = pcon.Get() |
76 |
- mountLabel = mcon.Get() |
|
77 | 76 |
selinux.ReserveLabel(processLabel) |
78 | 77 |
} |
78 |
+ mountLabel = mcon.Get() |
|
79 | 79 |
} |
80 | 80 |
return processLabel, mountLabel, nil |
81 | 81 |
} |
... | ... |
@@ -30,7 +30,6 @@ func Relabel(path string, fileLabel string, shared bool) error { |
30 | 30 |
// DisableSecOpt returns a security opt that can disable labeling |
31 | 31 |
// support for future container processes |
32 | 32 |
func DisableSecOpt() []string { |
33 |
- // TODO the selinux.DisableSecOpt stub returns []string{"disable"} instead of "nil" |
|
34 | 33 |
return nil |
35 | 34 |
} |
36 | 35 |
|
37 | 36 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,249 @@ |
0 |
+package selinux |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "github.com/pkg/errors" |
|
4 |
+) |
|
5 |
+ |
|
6 |
+const ( |
|
7 |
+ // Enforcing constant indicate SELinux is in enforcing mode |
|
8 |
+ Enforcing = 1 |
|
9 |
+ // Permissive constant to indicate SELinux is in permissive mode |
|
10 |
+ Permissive = 0 |
|
11 |
+ // Disabled constant to indicate SELinux is disabled |
|
12 |
+ Disabled = -1 |
|
13 |
+ |
|
14 |
+ // DefaultCategoryRange is the upper bound on the category range |
|
15 |
+ DefaultCategoryRange = uint32(1024) |
|
16 |
+) |
|
17 |
+ |
|
18 |
+var ( |
|
19 |
+ // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS. |
|
20 |
+ ErrMCSAlreadyExists = errors.New("MCS label already exists") |
|
21 |
+ // ErrEmptyPath is returned when an empty path has been specified. |
|
22 |
+ ErrEmptyPath = errors.New("empty path") |
|
23 |
+ |
|
24 |
+ // InvalidLabel is returned when an invalid label is specified. |
|
25 |
+ InvalidLabel = errors.New("Invalid Label") |
|
26 |
+ |
|
27 |
+ // ErrIncomparable is returned two levels are not comparable |
|
28 |
+ ErrIncomparable = errors.New("incomparable levels") |
|
29 |
+ // ErrLevelSyntax is returned when a sensitivity or category do not have correct syntax in a level |
|
30 |
+ ErrLevelSyntax = errors.New("invalid level syntax") |
|
31 |
+ |
|
32 |
+ // CategoryRange allows the upper bound on the category range to be adjusted |
|
33 |
+ CategoryRange = DefaultCategoryRange |
|
34 |
+) |
|
35 |
+ |
|
36 |
+// Context is a representation of the SELinux label broken into 4 parts |
|
37 |
+type Context map[string]string |
|
38 |
+ |
|
39 |
+// SetDisabled disables SELinux support for the package |
|
40 |
+func SetDisabled() { |
|
41 |
+ setDisabled() |
|
42 |
+} |
|
43 |
+ |
|
44 |
+// GetEnabled returns whether SELinux is currently enabled. |
|
45 |
+func GetEnabled() bool { |
|
46 |
+ return getEnabled() |
|
47 |
+} |
|
48 |
+ |
|
49 |
+// ClassIndex returns the int index for an object class in the loaded policy, |
|
50 |
+// or -1 and an error |
|
51 |
+func ClassIndex(class string) (int, error) { |
|
52 |
+ return classIndex(class) |
|
53 |
+} |
|
54 |
+ |
|
55 |
+// SetFileLabel sets the SELinux label for this path or returns an error. |
|
56 |
+func SetFileLabel(fpath string, label string) error { |
|
57 |
+ return setFileLabel(fpath, label) |
|
58 |
+} |
|
59 |
+ |
|
60 |
+// FileLabel returns the SELinux label for this path or returns an error. |
|
61 |
+func FileLabel(fpath string) (string, error) { |
|
62 |
+ return fileLabel(fpath) |
|
63 |
+} |
|
64 |
+ |
|
65 |
+// SetFSCreateLabel tells kernel the label to create all file system objects |
|
66 |
+// created by this task. Setting label="" to return to default. |
|
67 |
+func SetFSCreateLabel(label string) error { |
|
68 |
+ return setFSCreateLabel(label) |
|
69 |
+} |
|
70 |
+ |
|
71 |
+// FSCreateLabel returns the default label the kernel which the kernel is using |
|
72 |
+// for file system objects created by this task. "" indicates default. |
|
73 |
+func FSCreateLabel() (string, error) { |
|
74 |
+ return fsCreateLabel() |
|
75 |
+} |
|
76 |
+ |
|
77 |
+// CurrentLabel returns the SELinux label of the current process thread, or an error. |
|
78 |
+func CurrentLabel() (string, error) { |
|
79 |
+ return currentLabel() |
|
80 |
+} |
|
81 |
+ |
|
82 |
+// PidLabel returns the SELinux label of the given pid, or an error. |
|
83 |
+func PidLabel(pid int) (string, error) { |
|
84 |
+ return pidLabel(pid) |
|
85 |
+} |
|
86 |
+ |
|
87 |
+// ExecLabel returns the SELinux label that the kernel will use for any programs |
|
88 |
+// that are executed by the current process thread, or an error. |
|
89 |
+func ExecLabel() (string, error) { |
|
90 |
+ return execLabel() |
|
91 |
+} |
|
92 |
+ |
|
93 |
+// CanonicalizeContext takes a context string and writes it to the kernel |
|
94 |
+// the function then returns the context that the kernel will use. Use this |
|
95 |
+// function to check if two contexts are equivalent |
|
96 |
+func CanonicalizeContext(val string) (string, error) { |
|
97 |
+ return canonicalizeContext(val) |
|
98 |
+} |
|
99 |
+ |
|
100 |
+// ComputeCreateContext requests the type transition from source to target for |
|
101 |
+// class from the kernel. |
|
102 |
+func ComputeCreateContext(source string, target string, class string) (string, error) { |
|
103 |
+ return computeCreateContext(source, target, class) |
|
104 |
+} |
|
105 |
+ |
|
106 |
+// CalculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound) |
|
107 |
+// of a source and target range. |
|
108 |
+// The glblub is calculated as the greater of the low sensitivities and |
|
109 |
+// the lower of the high sensitivities and the and of each category bitset. |
|
110 |
+func CalculateGlbLub(sourceRange, targetRange string) (string, error) { |
|
111 |
+ return calculateGlbLub(sourceRange, targetRange) |
|
112 |
+} |
|
113 |
+ |
|
114 |
+// SetExecLabel sets the SELinux label that the kernel will use for any programs |
|
115 |
+// that are executed by the current process thread, or an error. |
|
116 |
+func SetExecLabel(label string) error { |
|
117 |
+ return setExecLabel(label) |
|
118 |
+} |
|
119 |
+ |
|
120 |
+// SetTaskLabel sets the SELinux label for the current thread, or an error. |
|
121 |
+// This requires the dyntransition permission. |
|
122 |
+func SetTaskLabel(label string) error { |
|
123 |
+ return setTaskLabel(label) |
|
124 |
+} |
|
125 |
+ |
|
126 |
+// SetSocketLabel takes a process label and tells the kernel to assign the |
|
127 |
+// label to the next socket that gets created |
|
128 |
+func SetSocketLabel(label string) error { |
|
129 |
+ return setSocketLabel(label) |
|
130 |
+} |
|
131 |
+ |
|
132 |
+// SocketLabel retrieves the current socket label setting |
|
133 |
+func SocketLabel() (string, error) { |
|
134 |
+ return socketLabel() |
|
135 |
+} |
|
136 |
+ |
|
137 |
+// PeerLabel retrieves the label of the client on the other side of a socket |
|
138 |
+func PeerLabel(fd uintptr) (string, error) { |
|
139 |
+ return peerLabel(fd) |
|
140 |
+} |
|
141 |
+ |
|
142 |
+// SetKeyLabel takes a process label and tells the kernel to assign the |
|
143 |
+// label to the next kernel keyring that gets created |
|
144 |
+func SetKeyLabel(label string) error { |
|
145 |
+ return setKeyLabel(label) |
|
146 |
+} |
|
147 |
+ |
|
148 |
+// KeyLabel retrieves the current kernel keyring label setting |
|
149 |
+func KeyLabel() (string, error) { |
|
150 |
+ return keyLabel() |
|
151 |
+} |
|
152 |
+ |
|
153 |
+// Get returns the Context as a string |
|
154 |
+func (c Context) Get() string { |
|
155 |
+ return c.get() |
|
156 |
+} |
|
157 |
+ |
|
158 |
+// NewContext creates a new Context struct from the specified label |
|
159 |
+func NewContext(label string) (Context, error) { |
|
160 |
+ return newContext(label) |
|
161 |
+} |
|
162 |
+ |
|
163 |
+// ClearLabels clears all reserved labels |
|
164 |
+func ClearLabels() { |
|
165 |
+ clearLabels() |
|
166 |
+} |
|
167 |
+ |
|
168 |
+// ReserveLabel reserves the MLS/MCS level component of the specified label |
|
169 |
+func ReserveLabel(label string) { |
|
170 |
+ reserveLabel(label) |
|
171 |
+} |
|
172 |
+ |
|
173 |
+// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled |
|
174 |
+func EnforceMode() int { |
|
175 |
+ return enforceMode() |
|
176 |
+} |
|
177 |
+ |
|
178 |
+// SetEnforceMode sets the current SELinux mode Enforcing, Permissive. |
|
179 |
+// Disabled is not valid, since this needs to be set at boot time. |
|
180 |
+func SetEnforceMode(mode int) error { |
|
181 |
+ return setEnforceMode(mode) |
|
182 |
+} |
|
183 |
+ |
|
184 |
+// DefaultEnforceMode returns the systems default SELinux mode Enforcing, |
|
185 |
+// Permissive or Disabled. Note this is is just the default at boot time. |
|
186 |
+// EnforceMode tells you the systems current mode. |
|
187 |
+func DefaultEnforceMode() int { |
|
188 |
+ return defaultEnforceMode() |
|
189 |
+} |
|
190 |
+ |
|
191 |
+// ReleaseLabel un-reserves the MLS/MCS Level field of the specified label, |
|
192 |
+// allowing it to be used by another process. |
|
193 |
+func ReleaseLabel(label string) { |
|
194 |
+ releaseLabel(label) |
|
195 |
+} |
|
196 |
+ |
|
197 |
+// ROFileLabel returns the specified SELinux readonly file label |
|
198 |
+func ROFileLabel() string { |
|
199 |
+ return roFileLabel() |
|
200 |
+} |
|
201 |
+ |
|
202 |
+// KVMContainerLabels returns the default processLabel and mountLabel to be used |
|
203 |
+// for kvm containers by the calling process. |
|
204 |
+func KVMContainerLabels() (string, string) { |
|
205 |
+ return kvmContainerLabels() |
|
206 |
+} |
|
207 |
+ |
|
208 |
+// InitContainerLabels returns the default processLabel and file labels to be |
|
209 |
+// used for containers running an init system like systemd by the calling process. |
|
210 |
+func InitContainerLabels() (string, string) { |
|
211 |
+ return initContainerLabels() |
|
212 |
+} |
|
213 |
+ |
|
214 |
+// ContainerLabels returns an allocated processLabel and fileLabel to be used for |
|
215 |
+// container labeling by the calling process. |
|
216 |
+func ContainerLabels() (processLabel string, fileLabel string) { |
|
217 |
+ return containerLabels() |
|
218 |
+} |
|
219 |
+ |
|
220 |
+// SecurityCheckContext validates that the SELinux label is understood by the kernel |
|
221 |
+func SecurityCheckContext(val string) error { |
|
222 |
+ return securityCheckContext(val) |
|
223 |
+} |
|
224 |
+ |
|
225 |
+// CopyLevel returns a label with the MLS/MCS level from src label replaced on |
|
226 |
+// the dest label. |
|
227 |
+func CopyLevel(src, dest string) (string, error) { |
|
228 |
+ return copyLevel(src, dest) |
|
229 |
+} |
|
230 |
+ |
|
231 |
+// Chcon changes the fpath file object to the SELinux label label. |
|
232 |
+// If fpath is a directory and recurse is true, then Chcon walks the |
|
233 |
+// directory tree setting the label. |
|
234 |
+func Chcon(fpath string, label string, recurse bool) error { |
|
235 |
+ return chcon(fpath, label, recurse) |
|
236 |
+} |
|
237 |
+ |
|
238 |
+// DupSecOpt takes an SELinux process label and returns security options that |
|
239 |
+// can be used to set the SELinux Type and Level for future container processes. |
|
240 |
+func DupSecOpt(src string) ([]string, error) { |
|
241 |
+ return dupSecOpt(src) |
|
242 |
+} |
|
243 |
+ |
|
244 |
+// DisableSecOpt returns a security opt that can be used to disable SELinux |
|
245 |
+// labeling support for future container processes. |
|
246 |
+func DisableSecOpt() []string { |
|
247 |
+ return disableSecOpt() |
|
248 |
+} |
... | ... |
@@ -20,17 +20,12 @@ import ( |
20 | 20 |
|
21 | 21 |
"github.com/opencontainers/selinux/pkg/pwalk" |
22 | 22 |
"github.com/pkg/errors" |
23 |
+ "github.com/willf/bitset" |
|
23 | 24 |
"golang.org/x/sys/unix" |
24 | 25 |
) |
25 | 26 |
|
26 | 27 |
const ( |
27 |
- // Enforcing constant indicate SELinux is in enforcing mode |
|
28 |
- Enforcing = 1 |
|
29 |
- // Permissive constant to indicate SELinux is in permissive mode |
|
30 |
- Permissive = 0 |
|
31 |
- // Disabled constant to indicate SELinux is disabled |
|
32 |
- Disabled = -1 |
|
33 |
- |
|
28 |
+ minSensLen = 2 |
|
34 | 29 |
contextFile = "/usr/share/containers/selinux/contexts" |
35 | 30 |
selinuxDir = "/etc/selinux/" |
36 | 31 |
selinuxConfig = selinuxDir + "config" |
... | ... |
@@ -49,17 +44,27 @@ type selinuxState struct { |
49 | 49 |
sync.Mutex |
50 | 50 |
} |
51 | 51 |
|
52 |
+type level struct { |
|
53 |
+ sens uint |
|
54 |
+ cats *bitset.BitSet |
|
55 |
+} |
|
56 |
+ |
|
57 |
+type mlsRange struct { |
|
58 |
+ low *level |
|
59 |
+ high *level |
|
60 |
+} |
|
61 |
+ |
|
62 |
+type levelItem byte |
|
63 |
+ |
|
64 |
+const ( |
|
65 |
+ sensitivity levelItem = 's' |
|
66 |
+ category levelItem = 'c' |
|
67 |
+) |
|
68 |
+ |
|
52 | 69 |
var ( |
53 |
- // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS. |
|
54 |
- ErrMCSAlreadyExists = errors.New("MCS label already exists") |
|
55 |
- // ErrEmptyPath is returned when an empty path has been specified. |
|
56 |
- ErrEmptyPath = errors.New("empty path") |
|
57 |
- // InvalidLabel is returned when an invalid label is specified. |
|
58 |
- InvalidLabel = errors.New("Invalid Label") |
|
59 |
- |
|
60 |
- assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) |
|
61 |
- roFileLabel string |
|
62 |
- state = selinuxState{ |
|
70 |
+ assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) |
|
71 |
+ readOnlyFileLabel string |
|
72 |
+ state = selinuxState{ |
|
63 | 73 |
mcsList: make(map[string]bool), |
64 | 74 |
} |
65 | 75 |
|
... | ... |
@@ -68,9 +73,6 @@ var ( |
68 | 68 |
haveThreadSelf bool |
69 | 69 |
) |
70 | 70 |
|
71 |
-// Context is a representation of the SELinux label broken into 4 parts |
|
72 |
-type Context map[string]string |
|
73 |
- |
|
74 | 71 |
func (s *selinuxState) setEnable(enabled bool) bool { |
75 | 72 |
s.Lock() |
76 | 73 |
defer s.Unlock() |
... | ... |
@@ -97,8 +99,8 @@ func (s *selinuxState) getEnabled() bool { |
97 | 97 |
return s.setEnable(enabled) |
98 | 98 |
} |
99 | 99 |
|
100 |
-// SetDisabled disables selinux support for the package |
|
101 |
-func SetDisabled() { |
|
100 |
+// setDisabled disables SELinux support for the package |
|
101 |
+func setDisabled() { |
|
102 | 102 |
state.setEnable(false) |
103 | 103 |
} |
104 | 104 |
|
... | ... |
@@ -190,15 +192,15 @@ func (s *selinuxState) getSELinuxfs() string { |
190 | 190 |
|
191 | 191 |
// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs |
192 | 192 |
// filesystem or an empty string if no mountpoint is found. Selinuxfs is |
193 |
-// a proc-like pseudo-filesystem that exposes the selinux policy API to |
|
193 |
+// a proc-like pseudo-filesystem that exposes the SELinux policy API to |
|
194 | 194 |
// processes. The existence of an selinuxfs mount is used to determine |
195 |
-// whether selinux is currently enabled or not. |
|
195 |
+// whether SELinux is currently enabled or not. |
|
196 | 196 |
func getSelinuxMountPoint() string { |
197 | 197 |
return state.getSELinuxfs() |
198 | 198 |
} |
199 | 199 |
|
200 |
-// GetEnabled returns whether selinux is currently enabled. |
|
201 |
-func GetEnabled() bool { |
|
200 |
+// getEnabled returns whether SELinux is currently enabled. |
|
201 |
+func getEnabled() bool { |
|
202 | 202 |
return state.getEnabled() |
203 | 203 |
} |
204 | 204 |
|
... | ... |
@@ -282,8 +284,9 @@ func readCon(fpath string) (string, error) { |
282 | 282 |
return strings.Trim(retval, "\x00"), nil |
283 | 283 |
} |
284 | 284 |
|
285 |
-// ClassIndex returns the int index for an object class in the loaded policy, or -1 and an error |
|
286 |
-func ClassIndex(class string) (int, error) { |
|
285 |
+// classIndex returns the int index for an object class in the loaded policy, |
|
286 |
+// or -1 and an error |
|
287 |
+func classIndex(class string) (int, error) { |
|
287 | 288 |
permpath := fmt.Sprintf("class/%s/index", class) |
288 | 289 |
indexpath := filepath.Join(getSelinuxMountPoint(), permpath) |
289 | 290 |
|
... | ... |
@@ -299,8 +302,8 @@ func ClassIndex(class string) (int, error) { |
299 | 299 |
return index, nil |
300 | 300 |
} |
301 | 301 |
|
302 |
-// SetFileLabel sets the SELinux label for this path or returns an error. |
|
303 |
-func SetFileLabel(fpath string, label string) error { |
|
302 |
+// setFileLabel sets the SELinux label for this path or returns an error. |
|
303 |
+func setFileLabel(fpath string, label string) error { |
|
304 | 304 |
if fpath == "" { |
305 | 305 |
return ErrEmptyPath |
306 | 306 |
} |
... | ... |
@@ -310,8 +313,8 @@ func SetFileLabel(fpath string, label string) error { |
310 | 310 |
return nil |
311 | 311 |
} |
312 | 312 |
|
313 |
-// FileLabel returns the SELinux label for this path or returns an error. |
|
314 |
-func FileLabel(fpath string) (string, error) { |
|
313 |
+// fileLabel returns the SELinux label for this path or returns an error. |
|
314 |
+func fileLabel(fpath string) (string, error) { |
|
315 | 315 |
if fpath == "" { |
316 | 316 |
return "", ErrEmptyPath |
317 | 317 |
} |
... | ... |
@@ -327,37 +330,31 @@ func FileLabel(fpath string) (string, error) { |
327 | 327 |
return string(label), nil |
328 | 328 |
} |
329 | 329 |
|
330 |
-/* |
|
331 |
-SetFSCreateLabel tells kernel the label to create all file system objects |
|
332 |
-created by this task. Setting label="" to return to default. |
|
333 |
-*/ |
|
334 |
-func SetFSCreateLabel(label string) error { |
|
330 |
+// setFSCreateLabel tells kernel the label to create all file system objects |
|
331 |
+// created by this task. Setting label="" to return to default. |
|
332 |
+func setFSCreateLabel(label string) error { |
|
335 | 333 |
return writeAttr("fscreate", label) |
336 | 334 |
} |
337 | 335 |
|
338 |
-/* |
|
339 |
-FSCreateLabel returns the default label the kernel which the kernel is using |
|
340 |
-for file system objects created by this task. "" indicates default. |
|
341 |
-*/ |
|
342 |
-func FSCreateLabel() (string, error) { |
|
336 |
+// fsCreateLabel returns the default label the kernel which the kernel is using |
|
337 |
+// for file system objects created by this task. "" indicates default. |
|
338 |
+func fsCreateLabel() (string, error) { |
|
343 | 339 |
return readAttr("fscreate") |
344 | 340 |
} |
345 | 341 |
|
346 |
-// CurrentLabel returns the SELinux label of the current process thread, or an error. |
|
347 |
-func CurrentLabel() (string, error) { |
|
342 |
+// currentLabel returns the SELinux label of the current process thread, or an error. |
|
343 |
+func currentLabel() (string, error) { |
|
348 | 344 |
return readAttr("current") |
349 | 345 |
} |
350 | 346 |
|
351 |
-// PidLabel returns the SELinux label of the given pid, or an error. |
|
352 |
-func PidLabel(pid int) (string, error) { |
|
347 |
+// pidLabel returns the SELinux label of the given pid, or an error. |
|
348 |
+func pidLabel(pid int) (string, error) { |
|
353 | 349 |
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid)) |
354 | 350 |
} |
355 | 351 |
|
356 |
-/* |
|
357 |
-ExecLabel returns the SELinux label that the kernel will use for any programs |
|
358 |
-that are executed by the current process thread, or an error. |
|
359 |
-*/ |
|
360 |
-func ExecLabel() (string, error) { |
|
352 |
+// ExecLabel returns the SELinux label that the kernel will use for any programs |
|
353 |
+// that are executed by the current process thread, or an error. |
|
354 |
+func execLabel() (string, error) { |
|
361 | 355 |
return readAttr("exec") |
362 | 356 |
} |
363 | 357 |
|
... | ... |
@@ -366,7 +363,7 @@ func writeCon(fpath, val string) error { |
366 | 366 |
return ErrEmptyPath |
367 | 367 |
} |
368 | 368 |
if val == "" { |
369 |
- if !GetEnabled() { |
|
369 |
+ if !getEnabled() { |
|
370 | 370 |
return nil |
371 | 371 |
} |
372 | 372 |
} |
... | ... |
@@ -418,20 +415,17 @@ func writeAttr(attr, val string) error { |
418 | 418 |
return writeCon(attrPath(attr), val) |
419 | 419 |
} |
420 | 420 |
|
421 |
-/* |
|
422 |
-CanonicalizeContext takes a context string and writes it to the kernel |
|
423 |
-the function then returns the context that the kernel will use. This function |
|
424 |
-can be used to see if two contexts are equivalent |
|
425 |
-*/ |
|
426 |
-func CanonicalizeContext(val string) (string, error) { |
|
421 |
+// canonicalizeContext takes a context string and writes it to the kernel |
|
422 |
+// the function then returns the context that the kernel will use. Use this |
|
423 |
+// function to check if two contexts are equivalent |
|
424 |
+func canonicalizeContext(val string) (string, error) { |
|
427 | 425 |
return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val) |
428 | 426 |
} |
429 | 427 |
|
430 |
-/* |
|
431 |
-ComputeCreateContext requests the type transition from source to target for class from the kernel. |
|
432 |
-*/ |
|
433 |
-func ComputeCreateContext(source string, target string, class string) (string, error) { |
|
434 |
- classidx, err := ClassIndex(class) |
|
428 |
+// computeCreateContext requests the type transition from source to target for |
|
429 |
+// class from the kernel. |
|
430 |
+func computeCreateContext(source string, target string, class string) (string, error) { |
|
431 |
+ classidx, err := classIndex(class) |
|
435 | 432 |
if err != nil { |
436 | 433 |
return "", err |
437 | 434 |
} |
... | ... |
@@ -439,6 +433,217 @@ func ComputeCreateContext(source string, target string, class string) (string, e |
439 | 439 |
return readWriteCon(filepath.Join(getSelinuxMountPoint(), "create"), fmt.Sprintf("%s %s %d", source, target, classidx)) |
440 | 440 |
} |
441 | 441 |
|
442 |
+// catsToBitset stores categories in a bitset. |
|
443 |
+func catsToBitset(cats string) (*bitset.BitSet, error) { |
|
444 |
+ bitset := &bitset.BitSet{} |
|
445 |
+ |
|
446 |
+ catlist := strings.Split(cats, ",") |
|
447 |
+ for _, r := range catlist { |
|
448 |
+ ranges := strings.SplitN(r, ".", 2) |
|
449 |
+ if len(ranges) > 1 { |
|
450 |
+ catstart, err := parseLevelItem(ranges[0], category) |
|
451 |
+ if err != nil { |
|
452 |
+ return nil, err |
|
453 |
+ } |
|
454 |
+ catend, err := parseLevelItem(ranges[1], category) |
|
455 |
+ if err != nil { |
|
456 |
+ return nil, err |
|
457 |
+ } |
|
458 |
+ for i := catstart; i <= catend; i++ { |
|
459 |
+ bitset.Set(i) |
|
460 |
+ } |
|
461 |
+ } else { |
|
462 |
+ cat, err := parseLevelItem(ranges[0], category) |
|
463 |
+ if err != nil { |
|
464 |
+ return nil, err |
|
465 |
+ } |
|
466 |
+ bitset.Set(cat) |
|
467 |
+ } |
|
468 |
+ } |
|
469 |
+ |
|
470 |
+ return bitset, nil |
|
471 |
+} |
|
472 |
+ |
|
473 |
+// parseLevelItem parses and verifies that a sensitivity or category are valid |
|
474 |
+func parseLevelItem(s string, sep levelItem) (uint, error) { |
|
475 |
+ if len(s) < minSensLen || levelItem(s[0]) != sep { |
|
476 |
+ return 0, ErrLevelSyntax |
|
477 |
+ } |
|
478 |
+ val, err := strconv.ParseUint(s[1:], 10, 32) |
|
479 |
+ if err != nil { |
|
480 |
+ return 0, err |
|
481 |
+ } |
|
482 |
+ |
|
483 |
+ return uint(val), nil |
|
484 |
+} |
|
485 |
+ |
|
486 |
+// parseLevel fills a level from a string that contains |
|
487 |
+// a sensitivity and categories |
|
488 |
+func (l *level) parseLevel(levelStr string) error { |
|
489 |
+ lvl := strings.SplitN(levelStr, ":", 2) |
|
490 |
+ sens, err := parseLevelItem(lvl[0], sensitivity) |
|
491 |
+ if err != nil { |
|
492 |
+ return errors.Wrap(err, "failed to parse sensitivity") |
|
493 |
+ } |
|
494 |
+ l.sens = sens |
|
495 |
+ if len(lvl) > 1 { |
|
496 |
+ cats, err := catsToBitset(lvl[1]) |
|
497 |
+ if err != nil { |
|
498 |
+ return errors.Wrap(err, "failed to parse categories") |
|
499 |
+ } |
|
500 |
+ l.cats = cats |
|
501 |
+ } |
|
502 |
+ |
|
503 |
+ return nil |
|
504 |
+} |
|
505 |
+ |
|
506 |
+// rangeStrToMLSRange marshals a string representation of a range. |
|
507 |
+func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) { |
|
508 |
+ mlsRange := &mlsRange{} |
|
509 |
+ levelSlice := strings.SplitN(rangeStr, "-", 2) |
|
510 |
+ |
|
511 |
+ switch len(levelSlice) { |
|
512 |
+ // rangeStr that has a low and a high level, e.g. s4:c0.c1023-s6:c0.c1023 |
|
513 |
+ case 2: |
|
514 |
+ mlsRange.high = &level{} |
|
515 |
+ if err := mlsRange.high.parseLevel(levelSlice[1]); err != nil { |
|
516 |
+ return nil, errors.Wrapf(err, "failed to parse high level %q", levelSlice[1]) |
|
517 |
+ } |
|
518 |
+ fallthrough |
|
519 |
+ // rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023 |
|
520 |
+ case 1: |
|
521 |
+ mlsRange.low = &level{} |
|
522 |
+ if err := mlsRange.low.parseLevel(levelSlice[0]); err != nil { |
|
523 |
+ return nil, errors.Wrapf(err, "failed to parse low level %q", levelSlice[0]) |
|
524 |
+ } |
|
525 |
+ } |
|
526 |
+ |
|
527 |
+ if mlsRange.high == nil { |
|
528 |
+ mlsRange.high = mlsRange.low |
|
529 |
+ } |
|
530 |
+ |
|
531 |
+ return mlsRange, nil |
|
532 |
+} |
|
533 |
+ |
|
534 |
+// bitsetToStr takes a category bitset and returns it in the |
|
535 |
+// canonical selinux syntax |
|
536 |
+func bitsetToStr(c *bitset.BitSet) string { |
|
537 |
+ var str string |
|
538 |
+ i, e := c.NextSet(0) |
|
539 |
+ len := 0 |
|
540 |
+ for e { |
|
541 |
+ if len == 0 { |
|
542 |
+ if str != "" { |
|
543 |
+ str += "," |
|
544 |
+ } |
|
545 |
+ str += "c" + strconv.Itoa(int(i)) |
|
546 |
+ } |
|
547 |
+ |
|
548 |
+ next, e := c.NextSet(i + 1) |
|
549 |
+ if e { |
|
550 |
+ // consecutive cats |
|
551 |
+ if next == i+1 { |
|
552 |
+ len++ |
|
553 |
+ i = next |
|
554 |
+ continue |
|
555 |
+ } |
|
556 |
+ } |
|
557 |
+ if len == 1 { |
|
558 |
+ str += ",c" + strconv.Itoa(int(i)) |
|
559 |
+ } else if len > 1 { |
|
560 |
+ str += ".c" + strconv.Itoa(int(i)) |
|
561 |
+ } |
|
562 |
+ if !e { |
|
563 |
+ break |
|
564 |
+ } |
|
565 |
+ len = 0 |
|
566 |
+ i = next |
|
567 |
+ } |
|
568 |
+ |
|
569 |
+ return str |
|
570 |
+} |
|
571 |
+ |
|
572 |
+func (l1 *level) equal(l2 *level) bool { |
|
573 |
+ if l2 == nil || l1 == nil { |
|
574 |
+ return l1 == l2 |
|
575 |
+ } |
|
576 |
+ if l1.sens != l2.sens { |
|
577 |
+ return false |
|
578 |
+ } |
|
579 |
+ return l1.cats.Equal(l2.cats) |
|
580 |
+} |
|
581 |
+ |
|
582 |
+// String returns an mlsRange as a string. |
|
583 |
+func (m mlsRange) String() string { |
|
584 |
+ low := "s" + strconv.Itoa(int(m.low.sens)) |
|
585 |
+ if m.low.cats != nil && m.low.cats.Count() > 0 { |
|
586 |
+ low += ":" + bitsetToStr(m.low.cats) |
|
587 |
+ } |
|
588 |
+ |
|
589 |
+ if m.low.equal(m.high) { |
|
590 |
+ return low |
|
591 |
+ } |
|
592 |
+ |
|
593 |
+ high := "s" + strconv.Itoa(int(m.high.sens)) |
|
594 |
+ if m.high.cats != nil && m.high.cats.Count() > 0 { |
|
595 |
+ high += ":" + bitsetToStr(m.high.cats) |
|
596 |
+ } |
|
597 |
+ |
|
598 |
+ return low + "-" + high |
|
599 |
+} |
|
600 |
+ |
|
601 |
+func max(a, b uint) uint { |
|
602 |
+ if a > b { |
|
603 |
+ return a |
|
604 |
+ } |
|
605 |
+ return b |
|
606 |
+} |
|
607 |
+ |
|
608 |
+func min(a, b uint) uint { |
|
609 |
+ if a < b { |
|
610 |
+ return a |
|
611 |
+ } |
|
612 |
+ return b |
|
613 |
+} |
|
614 |
+ |
|
615 |
+// calculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound) |
|
616 |
+// of a source and target range. |
|
617 |
+// The glblub is calculated as the greater of the low sensitivities and |
|
618 |
+// the lower of the high sensitivities and the and of each category bitset. |
|
619 |
+func calculateGlbLub(sourceRange, targetRange string) (string, error) { |
|
620 |
+ s, err := rangeStrToMLSRange(sourceRange) |
|
621 |
+ if err != nil { |
|
622 |
+ return "", err |
|
623 |
+ } |
|
624 |
+ t, err := rangeStrToMLSRange(targetRange) |
|
625 |
+ if err != nil { |
|
626 |
+ return "", err |
|
627 |
+ } |
|
628 |
+ |
|
629 |
+ if s.high.sens < t.low.sens || t.high.sens < s.low.sens { |
|
630 |
+ /* these ranges have no common sensitivities */ |
|
631 |
+ return "", ErrIncomparable |
|
632 |
+ } |
|
633 |
+ |
|
634 |
+ outrange := &mlsRange{low: &level{}, high: &level{}} |
|
635 |
+ |
|
636 |
+ /* take the greatest of the low */ |
|
637 |
+ outrange.low.sens = max(s.low.sens, t.low.sens) |
|
638 |
+ |
|
639 |
+ /* take the least of the high */ |
|
640 |
+ outrange.high.sens = min(s.high.sens, t.high.sens) |
|
641 |
+ |
|
642 |
+ /* find the intersecting categories */ |
|
643 |
+ if s.low.cats != nil && t.low.cats != nil { |
|
644 |
+ outrange.low.cats = s.low.cats.Intersection(t.low.cats) |
|
645 |
+ } |
|
646 |
+ if s.high.cats != nil && t.high.cats != nil { |
|
647 |
+ outrange.high.cats = s.high.cats.Intersection(t.high.cats) |
|
648 |
+ } |
|
649 |
+ |
|
650 |
+ return outrange.String(), nil |
|
651 |
+} |
|
652 |
+ |
|
442 | 653 |
func readWriteCon(fpath string, val string) (string, error) { |
443 | 654 |
if fpath == "" { |
444 | 655 |
return "", ErrEmptyPath |
... | ... |
@@ -461,41 +666,37 @@ func readWriteCon(fpath string, val string) (string, error) { |
461 | 461 |
return strings.Trim(retval, "\x00"), nil |
462 | 462 |
} |
463 | 463 |
|
464 |
-/* |
|
465 |
-SetExecLabel sets the SELinux label that the kernel will use for any programs |
|
466 |
-that are executed by the current process thread, or an error. |
|
467 |
-*/ |
|
468 |
-func SetExecLabel(label string) error { |
|
464 |
+// setExecLabel sets the SELinux label that the kernel will use for any programs |
|
465 |
+// that are executed by the current process thread, or an error. |
|
466 |
+func setExecLabel(label string) error { |
|
469 | 467 |
return writeAttr("exec", label) |
470 | 468 |
} |
471 | 469 |
|
472 |
-/* |
|
473 |
-SetTaskLabel sets the SELinux label for the current thread, or an error. |
|
474 |
-This requires the dyntransition permission. |
|
475 |
-*/ |
|
476 |
-func SetTaskLabel(label string) error { |
|
470 |
+// setTaskLabel sets the SELinux label for the current thread, or an error. |
|
471 |
+// This requires the dyntransition permission. |
|
472 |
+func setTaskLabel(label string) error { |
|
477 | 473 |
return writeAttr("current", label) |
478 | 474 |
} |
479 | 475 |
|
480 |
-// SetSocketLabel takes a process label and tells the kernel to assign the |
|
476 |
+// setSocketLabel takes a process label and tells the kernel to assign the |
|
481 | 477 |
// label to the next socket that gets created |
482 |
-func SetSocketLabel(label string) error { |
|
478 |
+func setSocketLabel(label string) error { |
|
483 | 479 |
return writeAttr("sockcreate", label) |
484 | 480 |
} |
485 | 481 |
|
486 |
-// SocketLabel retrieves the current socket label setting |
|
487 |
-func SocketLabel() (string, error) { |
|
482 |
+// socketLabel retrieves the current socket label setting |
|
483 |
+func socketLabel() (string, error) { |
|
488 | 484 |
return readAttr("sockcreate") |
489 | 485 |
} |
490 | 486 |
|
491 |
-// PeerLabel retrieves the label of the client on the other side of a socket |
|
492 |
-func PeerLabel(fd uintptr) (string, error) { |
|
487 |
+// peerLabel retrieves the label of the client on the other side of a socket |
|
488 |
+func peerLabel(fd uintptr) (string, error) { |
|
493 | 489 |
return unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC) |
494 | 490 |
} |
495 | 491 |
|
496 |
-// SetKeyLabel takes a process label and tells the kernel to assign the |
|
492 |
+// setKeyLabel takes a process label and tells the kernel to assign the |
|
497 | 493 |
// label to the next kernel keyring that gets created |
498 |
-func SetKeyLabel(label string) error { |
|
494 |
+func setKeyLabel(label string) error { |
|
499 | 495 |
err := writeCon("/proc/self/attr/keycreate", label) |
500 | 496 |
if os.IsNotExist(errors.Cause(err)) { |
501 | 497 |
return nil |
... | ... |
@@ -506,21 +707,21 @@ func SetKeyLabel(label string) error { |
506 | 506 |
return err |
507 | 507 |
} |
508 | 508 |
|
509 |
-// KeyLabel retrieves the current kernel keyring label setting |
|
510 |
-func KeyLabel() (string, error) { |
|
509 |
+// keyLabel retrieves the current kernel keyring label setting |
|
510 |
+func keyLabel() (string, error) { |
|
511 | 511 |
return readCon("/proc/self/attr/keycreate") |
512 | 512 |
} |
513 | 513 |
|
514 |
-// Get returns the Context as a string |
|
515 |
-func (c Context) Get() string { |
|
514 |
+// get returns the Context as a string |
|
515 |
+func (c Context) get() string { |
|
516 | 516 |
if c["level"] != "" { |
517 | 517 |
return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) |
518 | 518 |
} |
519 | 519 |
return fmt.Sprintf("%s:%s:%s", c["user"], c["role"], c["type"]) |
520 | 520 |
} |
521 | 521 |
|
522 |
-// NewContext creates a new Context struct from the specified label |
|
523 |
-func NewContext(label string) (Context, error) { |
|
522 |
+// newContext creates a new Context struct from the specified label |
|
523 |
+func newContext(label string) (Context, error) { |
|
524 | 524 |
c := make(Context) |
525 | 525 |
|
526 | 526 |
if len(label) != 0 { |
... | ... |
@@ -538,15 +739,15 @@ func NewContext(label string) (Context, error) { |
538 | 538 |
return c, nil |
539 | 539 |
} |
540 | 540 |
|
541 |
-// ClearLabels clears all reserved labels |
|
542 |
-func ClearLabels() { |
|
541 |
+// clearLabels clears all reserved labels |
|
542 |
+func clearLabels() { |
|
543 | 543 |
state.Lock() |
544 | 544 |
state.mcsList = make(map[string]bool) |
545 | 545 |
state.Unlock() |
546 | 546 |
} |
547 | 547 |
|
548 |
-// ReserveLabel reserves the MLS/MCS level component of the specified label |
|
549 |
-func ReserveLabel(label string) { |
|
548 |
+// reserveLabel reserves the MLS/MCS level component of the specified label |
|
549 |
+func reserveLabel(label string) { |
|
550 | 550 |
if len(label) != 0 { |
551 | 551 |
con := strings.SplitN(label, ":", 4) |
552 | 552 |
if len(con) > 3 { |
... | ... |
@@ -559,8 +760,8 @@ func selinuxEnforcePath() string { |
559 | 559 |
return path.Join(getSelinuxMountPoint(), "enforce") |
560 | 560 |
} |
561 | 561 |
|
562 |
-// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled |
|
563 |
-func EnforceMode() int { |
|
562 |
+// enforceMode returns the current SELinux mode Enforcing, Permissive, Disabled |
|
563 |
+func enforceMode() int { |
|
564 | 564 |
var enforce int |
565 | 565 |
|
566 | 566 |
enforceB, err := ioutil.ReadFile(selinuxEnforcePath()) |
... | ... |
@@ -574,20 +775,16 @@ func EnforceMode() int { |
574 | 574 |
return enforce |
575 | 575 |
} |
576 | 576 |
|
577 |
-/* |
|
578 |
-SetEnforceMode sets the current SELinux mode Enforcing, Permissive. |
|
579 |
-Disabled is not valid, since this needs to be set at boot time. |
|
580 |
-*/ |
|
581 |
-func SetEnforceMode(mode int) error { |
|
577 |
+// setEnforceMode sets the current SELinux mode Enforcing, Permissive. |
|
578 |
+// Disabled is not valid, since this needs to be set at boot time. |
|
579 |
+func setEnforceMode(mode int) error { |
|
582 | 580 |
return ioutil.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0644) |
583 | 581 |
} |
584 | 582 |
|
585 |
-/* |
|
586 |
-DefaultEnforceMode returns the systems default SELinux mode Enforcing, |
|
587 |
-Permissive or Disabled. Note this is is just the default at boot time. |
|
588 |
-EnforceMode tells you the systems current mode. |
|
589 |
-*/ |
|
590 |
-func DefaultEnforceMode() int { |
|
583 |
+// defaultEnforceMode returns the systems default SELinux mode Enforcing, |
|
584 |
+// Permissive or Disabled. Note this is is just the default at boot time. |
|
585 |
+// EnforceMode tells you the systems current mode. |
|
586 |
+func defaultEnforceMode() int { |
|
591 | 587 |
switch readConfig(selinuxTag) { |
592 | 588 |
case "enforcing": |
593 | 589 |
return Enforcing |
... | ... |
@@ -667,11 +864,9 @@ func uniqMcs(catRange uint32) string { |
667 | 667 |
return mcs |
668 | 668 |
} |
669 | 669 |
|
670 |
-/* |
|
671 |
-ReleaseLabel will unreserve the MLS/MCS Level field of the specified label. |
|
672 |
-Allowing it to be used by another process. |
|
673 |
-*/ |
|
674 |
-func ReleaseLabel(label string) { |
|
670 |
+// releaseLabel un-reserves the MLS/MCS Level field of the specified label, |
|
671 |
+// allowing it to be used by another process. |
|
672 |
+func releaseLabel(label string) { |
|
675 | 673 |
if len(label) != 0 { |
676 | 674 |
con := strings.SplitN(label, ":", 4) |
677 | 675 |
if len(con) > 3 { |
... | ... |
@@ -680,9 +875,9 @@ func ReleaseLabel(label string) { |
680 | 680 |
} |
681 | 681 |
} |
682 | 682 |
|
683 |
-// ROFileLabel returns the specified SELinux readonly file label |
|
684 |
-func ROFileLabel() string { |
|
685 |
- return roFileLabel |
|
683 |
+// roFileLabel returns the specified SELinux readonly file label |
|
684 |
+func roFileLabel() string { |
|
685 |
+ return readOnlyFileLabel |
|
686 | 686 |
} |
687 | 687 |
|
688 | 688 |
func openContextFile() (*os.File, error) { |
... | ... |
@@ -737,11 +932,9 @@ func loadLabels() map[string]string { |
737 | 737 |
return labels |
738 | 738 |
} |
739 | 739 |
|
740 |
-/* |
|
741 |
-KVMContainerLabels returns the default processLabel and mountLabel to be used |
|
742 |
-for kvm containers by the calling process. |
|
743 |
-*/ |
|
744 |
-func KVMContainerLabels() (string, string) { |
|
740 |
+// kvmContainerLabels returns the default processLabel and mountLabel to be used |
|
741 |
+// for kvm containers by the calling process. |
|
742 |
+func kvmContainerLabels() (string, string) { |
|
745 | 743 |
processLabel := labels["kvm_process"] |
746 | 744 |
if processLabel == "" { |
747 | 745 |
processLabel = labels["process"] |
... | ... |
@@ -750,11 +943,9 @@ func KVMContainerLabels() (string, string) { |
750 | 750 |
return addMcs(processLabel, labels["file"]) |
751 | 751 |
} |
752 | 752 |
|
753 |
-/* |
|
754 |
-InitContainerLabels returns the default processLabel and file labels to be |
|
755 |
-used for containers running an init system like systemd by the calling process. |
|
756 |
-*/ |
|
757 |
-func InitContainerLabels() (string, string) { |
|
753 |
+// initContainerLabels returns the default processLabel and file labels to be |
|
754 |
+// used for containers running an init system like systemd by the calling process. |
|
755 |
+func initContainerLabels() (string, string) { |
|
758 | 756 |
processLabel := labels["init_process"] |
759 | 757 |
if processLabel == "" { |
760 | 758 |
processLabel = labels["process"] |
... | ... |
@@ -763,25 +954,23 @@ func InitContainerLabels() (string, string) { |
763 | 763 |
return addMcs(processLabel, labels["file"]) |
764 | 764 |
} |
765 | 765 |
|
766 |
-/* |
|
767 |
-ContainerLabels returns an allocated processLabel and fileLabel to be used for |
|
768 |
-container labeling by the calling process. |
|
769 |
-*/ |
|
770 |
-func ContainerLabels() (processLabel string, fileLabel string) { |
|
771 |
- if !GetEnabled() { |
|
766 |
+// containerLabels returns an allocated processLabel and fileLabel to be used for |
|
767 |
+// container labeling by the calling process. |
|
768 |
+func containerLabels() (processLabel string, fileLabel string) { |
|
769 |
+ if !getEnabled() { |
|
772 | 770 |
return "", "" |
773 | 771 |
} |
774 | 772 |
|
775 | 773 |
processLabel = labels["process"] |
776 | 774 |
fileLabel = labels["file"] |
777 |
- roFileLabel = labels["ro_file"] |
|
775 |
+ readOnlyFileLabel = labels["ro_file"] |
|
778 | 776 |
|
779 | 777 |
if processLabel == "" || fileLabel == "" { |
780 | 778 |
return "", fileLabel |
781 | 779 |
} |
782 | 780 |
|
783 |
- if roFileLabel == "" { |
|
784 |
- roFileLabel = fileLabel |
|
781 |
+ if readOnlyFileLabel == "" { |
|
782 |
+ readOnlyFileLabel = fileLabel |
|
785 | 783 |
} |
786 | 784 |
|
787 | 785 |
return addMcs(processLabel, fileLabel) |
... | ... |
@@ -790,7 +979,7 @@ func ContainerLabels() (processLabel string, fileLabel string) { |
790 | 790 |
func addMcs(processLabel, fileLabel string) (string, string) { |
791 | 791 |
scon, _ := NewContext(processLabel) |
792 | 792 |
if scon["level"] != "" { |
793 |
- mcs := uniqMcs(1024) |
|
793 |
+ mcs := uniqMcs(CategoryRange) |
|
794 | 794 |
scon["level"] = mcs |
795 | 795 |
processLabel = scon.Get() |
796 | 796 |
scon, _ = NewContext(fileLabel) |
... | ... |
@@ -800,16 +989,14 @@ func addMcs(processLabel, fileLabel string) (string, string) { |
800 | 800 |
return processLabel, fileLabel |
801 | 801 |
} |
802 | 802 |
|
803 |
-// SecurityCheckContext validates that the SELinux label is understood by the kernel |
|
804 |
-func SecurityCheckContext(val string) error { |
|
803 |
+// securityCheckContext validates that the SELinux label is understood by the kernel |
|
804 |
+func securityCheckContext(val string) error { |
|
805 | 805 |
return ioutil.WriteFile(path.Join(getSelinuxMountPoint(), "context"), []byte(val), 0644) |
806 | 806 |
} |
807 | 807 |
|
808 |
-/* |
|
809 |
-CopyLevel returns a label with the MLS/MCS level from src label replaced on |
|
810 |
-the dest label. |
|
811 |
-*/ |
|
812 |
-func CopyLevel(src, dest string) (string, error) { |
|
808 |
+// copyLevel returns a label with the MLS/MCS level from src label replaced on |
|
809 |
+// the dest label. |
|
810 |
+func copyLevel(src, dest string) (string, error) { |
|
813 | 811 |
if src == "" { |
814 | 812 |
return "", nil |
815 | 813 |
} |
... | ... |
@@ -833,7 +1020,7 @@ func CopyLevel(src, dest string) (string, error) { |
833 | 833 |
return tcon.Get(), nil |
834 | 834 |
} |
835 | 835 |
|
836 |
-// Prevent users from relabing system files |
|
836 |
+// Prevent users from relabeling system files |
|
837 | 837 |
func badPrefix(fpath string) error { |
838 | 838 |
if fpath == "" { |
839 | 839 |
return ErrEmptyPath |
... | ... |
@@ -848,10 +1035,10 @@ func badPrefix(fpath string) error { |
848 | 848 |
return nil |
849 | 849 |
} |
850 | 850 |
|
851 |
-// Chcon changes the fpath file object to the SELinux label label. |
|
852 |
-// If fpath is a directory and recurse is true, Chcon will walk the |
|
851 |
+// chcon changes the fpath file object to the SELinux label label. |
|
852 |
+// If fpath is a directory and recurse is true, then chcon walks the |
|
853 | 853 |
// directory tree setting the label. |
854 |
-func Chcon(fpath string, label string, recurse bool) error { |
|
854 |
+func chcon(fpath string, label string, recurse bool) error { |
|
855 | 855 |
if fpath == "" { |
856 | 856 |
return ErrEmptyPath |
857 | 857 |
} |
... | ... |
@@ -876,9 +1063,9 @@ func Chcon(fpath string, label string, recurse bool) error { |
876 | 876 |
}) |
877 | 877 |
} |
878 | 878 |
|
879 |
-// DupSecOpt takes an SELinux process label and returns security options that |
|
879 |
+// dupSecOpt takes an SELinux process label and returns security options that |
|
880 | 880 |
// can be used to set the SELinux Type and Level for future container processes. |
881 |
-func DupSecOpt(src string) ([]string, error) { |
|
881 |
+func dupSecOpt(src string) ([]string, error) { |
|
882 | 882 |
if src == "" { |
883 | 883 |
return nil, nil |
884 | 884 |
} |
... | ... |
@@ -903,8 +1090,8 @@ func DupSecOpt(src string) ([]string, error) { |
903 | 903 |
return dup, nil |
904 | 904 |
} |
905 | 905 |
|
906 |
-// DisableSecOpt returns a security opt that can be used to disable SELinux |
|
906 |
+// disableSecOpt returns a security opt that can be used to disable SELinux |
|
907 | 907 |
// labeling support for future container processes. |
908 |
-func DisableSecOpt() []string { |
|
908 |
+func disableSecOpt() []string { |
|
909 | 909 |
return []string{"disable"} |
910 | 910 |
} |
... | ... |
@@ -2,253 +2,147 @@ |
2 | 2 |
|
3 | 3 |
package selinux |
4 | 4 |
|
5 |
-import ( |
|
6 |
- "errors" |
|
7 |
-) |
|
8 |
- |
|
9 |
-const ( |
|
10 |
- // Enforcing constant indicate SELinux is in enforcing mode |
|
11 |
- Enforcing = 1 |
|
12 |
- // Permissive constant to indicate SELinux is in permissive mode |
|
13 |
- Permissive = 0 |
|
14 |
- // Disabled constant to indicate SELinux is disabled |
|
15 |
- Disabled = -1 |
|
16 |
-) |
|
17 |
- |
|
18 |
-var ( |
|
19 |
- // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS. |
|
20 |
- ErrMCSAlreadyExists = errors.New("MCS label already exists") |
|
21 |
- // ErrEmptyPath is returned when an empty path has been specified. |
|
22 |
- ErrEmptyPath = errors.New("empty path") |
|
23 |
-) |
|
24 |
- |
|
25 |
-// Context is a representation of the SELinux label broken into 4 parts |
|
26 |
-type Context map[string]string |
|
27 |
- |
|
28 |
-// SetDisabled disables selinux support for the package |
|
29 |
-func SetDisabled() { |
|
30 |
- return |
|
31 |
-} |
|
32 |
- |
|
33 |
-// GetEnabled returns whether selinux is currently enabled. |
|
34 |
-func GetEnabled() bool { |
|
5 |
+func setDisabled() { |
|
6 |
+} |
|
7 |
+ |
|
8 |
+func getEnabled() bool { |
|
35 | 9 |
return false |
36 | 10 |
} |
37 | 11 |
|
38 |
-// ClassIndex returns the int index for an object class in the loaded policy, or -1 and an error |
|
39 |
-func ClassIndex(class string) (int, error) { |
|
12 |
+func classIndex(class string) (int, error) { |
|
40 | 13 |
return -1, nil |
41 | 14 |
} |
42 | 15 |
|
43 |
-// SetFileLabel sets the SELinux label for this path or returns an error. |
|
44 |
-func SetFileLabel(fpath string, label string) error { |
|
16 |
+func setFileLabel(fpath string, label string) error { |
|
45 | 17 |
return nil |
46 | 18 |
} |
47 | 19 |
|
48 |
-// FileLabel returns the SELinux label for this path or returns an error. |
|
49 |
-func FileLabel(fpath string) (string, error) { |
|
20 |
+func fileLabel(fpath string) (string, error) { |
|
50 | 21 |
return "", nil |
51 | 22 |
} |
52 | 23 |
|
53 |
-/* |
|
54 |
-SetFSCreateLabel tells kernel the label to create all file system objects |
|
55 |
-created by this task. Setting label="" to return to default. |
|
56 |
-*/ |
|
57 |
-func SetFSCreateLabel(label string) error { |
|
24 |
+func setFSCreateLabel(label string) error { |
|
58 | 25 |
return nil |
59 | 26 |
} |
60 | 27 |
|
61 |
-/* |
|
62 |
-FSCreateLabel returns the default label the kernel which the kernel is using |
|
63 |
-for file system objects created by this task. "" indicates default. |
|
64 |
-*/ |
|
65 |
-func FSCreateLabel() (string, error) { |
|
28 |
+func fsCreateLabel() (string, error) { |
|
29 |
+ return "", nil |
|
30 |
+} |
|
31 |
+ |
|
32 |
+func currentLabel() (string, error) { |
|
66 | 33 |
return "", nil |
67 | 34 |
} |
68 | 35 |
|
69 |
-// CurrentLabel returns the SELinux label of the current process thread, or an error. |
|
70 |
-func CurrentLabel() (string, error) { |
|
36 |
+func pidLabel(pid int) (string, error) { |
|
71 | 37 |
return "", nil |
72 | 38 |
} |
73 | 39 |
|
74 |
-// PidLabel returns the SELinux label of the given pid, or an error. |
|
75 |
-func PidLabel(pid int) (string, error) { |
|
40 |
+func execLabel() (string, error) { |
|
76 | 41 |
return "", nil |
77 | 42 |
} |
78 | 43 |
|
79 |
-/* |
|
80 |
-ExecLabel returns the SELinux label that the kernel will use for any programs |
|
81 |
-that are executed by the current process thread, or an error. |
|
82 |
-*/ |
|
83 |
-func ExecLabel() (string, error) { |
|
44 |
+func canonicalizeContext(val string) (string, error) { |
|
84 | 45 |
return "", nil |
85 | 46 |
} |
86 | 47 |
|
87 |
-/* |
|
88 |
-CanonicalizeContext takes a context string and writes it to the kernel |
|
89 |
-the function then returns the context that the kernel will use. This function |
|
90 |
-can be used to see if two contexts are equivalent |
|
91 |
-*/ |
|
92 |
-func CanonicalizeContext(val string) (string, error) { |
|
48 |
+func computeCreateContext(source string, target string, class string) (string, error) { |
|
93 | 49 |
return "", nil |
94 | 50 |
} |
95 | 51 |
|
96 |
-/* |
|
97 |
-ComputeCreateContext requests the type transition from source to target for class from the kernel. |
|
98 |
-*/ |
|
99 |
-func ComputeCreateContext(source string, target string, class string) (string, error) { |
|
52 |
+func calculateGlbLub(sourceRange, targetRange string) (string, error) { |
|
100 | 53 |
return "", nil |
101 | 54 |
} |
102 | 55 |
|
103 |
-/* |
|
104 |
-SetExecLabel sets the SELinux label that the kernel will use for any programs |
|
105 |
-that are executed by the current process thread, or an error. |
|
106 |
-*/ |
|
107 |
-func SetExecLabel(label string) error { |
|
56 |
+func setExecLabel(label string) error { |
|
108 | 57 |
return nil |
109 | 58 |
} |
110 | 59 |
|
111 |
-/* |
|
112 |
-SetTaskLabel sets the SELinux label for the current thread, or an error. |
|
113 |
-This requires the dyntransition permission. |
|
114 |
-*/ |
|
115 |
-func SetTaskLabel(label string) error { |
|
60 |
+func setTaskLabel(label string) error { |
|
116 | 61 |
return nil |
117 | 62 |
} |
118 | 63 |
|
119 |
-/* |
|
120 |
-SetSocketLabel sets the SELinux label that the kernel will use for any programs |
|
121 |
-that are executed by the current process thread, or an error. |
|
122 |
-*/ |
|
123 |
-func SetSocketLabel(label string) error { |
|
64 |
+func setSocketLabel(label string) error { |
|
124 | 65 |
return nil |
125 | 66 |
} |
126 | 67 |
|
127 |
-// SocketLabel retrieves the current socket label setting |
|
128 |
-func SocketLabel() (string, error) { |
|
68 |
+func socketLabel() (string, error) { |
|
129 | 69 |
return "", nil |
130 | 70 |
} |
131 | 71 |
|
132 |
-// PeerLabel retrieves the label of the client on the other side of a socket |
|
133 |
-func PeerLabel(fd uintptr) (string, error) { |
|
72 |
+func peerLabel(fd uintptr) (string, error) { |
|
134 | 73 |
return "", nil |
135 | 74 |
} |
136 | 75 |
|
137 |
-// SetKeyLabel takes a process label and tells the kernel to assign the |
|
138 |
-// label to the next kernel keyring that gets created |
|
139 |
-func SetKeyLabel(label string) error { |
|
76 |
+func setKeyLabel(label string) error { |
|
140 | 77 |
return nil |
141 | 78 |
} |
142 | 79 |
|
143 |
-// KeyLabel retrieves the current kernel keyring label setting |
|
144 |
-func KeyLabel() (string, error) { |
|
80 |
+func keyLabel() (string, error) { |
|
145 | 81 |
return "", nil |
146 | 82 |
} |
147 | 83 |
|
148 |
-// Get returns the Context as a string |
|
149 |
-func (c Context) Get() string { |
|
84 |
+func (c Context) get() string { |
|
150 | 85 |
return "" |
151 | 86 |
} |
152 | 87 |
|
153 |
-// NewContext creates a new Context struct from the specified label |
|
154 |
-func NewContext(label string) (Context, error) { |
|
88 |
+func newContext(label string) (Context, error) { |
|
155 | 89 |
c := make(Context) |
156 | 90 |
return c, nil |
157 | 91 |
} |
158 | 92 |
|
159 |
-// ClearLabels clears all reserved MLS/MCS levels |
|
160 |
-func ClearLabels() { |
|
161 |
- return |
|
93 |
+func clearLabels() { |
|
162 | 94 |
} |
163 | 95 |
|
164 |
-// ReserveLabel reserves the MLS/MCS level component of the specified label |
|
165 |
-func ReserveLabel(label string) { |
|
166 |
- return |
|
96 |
+func reserveLabel(label string) { |
|
167 | 97 |
} |
168 | 98 |
|
169 |
-// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled |
|
170 |
-func EnforceMode() int { |
|
99 |
+func enforceMode() int { |
|
171 | 100 |
return Disabled |
172 | 101 |
} |
173 | 102 |
|
174 |
-/* |
|
175 |
-SetEnforceMode sets the current SELinux mode Enforcing, Permissive. |
|
176 |
-Disabled is not valid, since this needs to be set at boot time. |
|
177 |
-*/ |
|
178 |
-func SetEnforceMode(mode int) error { |
|
103 |
+func setEnforceMode(mode int) error { |
|
179 | 104 |
return nil |
180 | 105 |
} |
181 | 106 |
|
182 |
-/* |
|
183 |
-DefaultEnforceMode returns the systems default SELinux mode Enforcing, |
|
184 |
-Permissive or Disabled. Note this is is just the default at boot time. |
|
185 |
-EnforceMode tells you the systems current mode. |
|
186 |
-*/ |
|
187 |
-func DefaultEnforceMode() int { |
|
107 |
+func defaultEnforceMode() int { |
|
188 | 108 |
return Disabled |
189 | 109 |
} |
190 | 110 |
|
191 |
-/* |
|
192 |
-ReleaseLabel will unreserve the MLS/MCS Level field of the specified label. |
|
193 |
-Allowing it to be used by another process. |
|
194 |
-*/ |
|
195 |
-func ReleaseLabel(label string) { |
|
196 |
- return |
|
111 |
+func releaseLabel(label string) { |
|
197 | 112 |
} |
198 | 113 |
|
199 |
-// ROFileLabel returns the specified SELinux readonly file label |
|
200 |
-func ROFileLabel() string { |
|
114 |
+func roFileLabel() string { |
|
201 | 115 |
return "" |
202 | 116 |
} |
203 | 117 |
|
204 |
-// KVMContainerLabels returns the default processLabel and mountLabel to be used |
|
205 |
-// for kvm containers by the calling process. |
|
206 |
-func KVMContainerLabels() (string, string) { |
|
118 |
+func kvmContainerLabels() (string, string) { |
|
207 | 119 |
return "", "" |
208 | 120 |
} |
209 | 121 |
|
210 |
-// InitContainerLabels returns the default processLabel and file labels to be |
|
211 |
-// used for containers running an init system like systemd by the calling |
|
212 |
-func InitContainerLabels() (string, string) { |
|
122 |
+func initContainerLabels() (string, string) { |
|
213 | 123 |
return "", "" |
214 | 124 |
} |
215 | 125 |
|
216 |
-/* |
|
217 |
-ContainerLabels returns an allocated processLabel and fileLabel to be used for |
|
218 |
-container labeling by the calling process. |
|
219 |
-*/ |
|
220 |
-func ContainerLabels() (processLabel string, fileLabel string) { |
|
126 |
+func containerLabels() (processLabel string, fileLabel string) { |
|
221 | 127 |
return "", "" |
222 | 128 |
} |
223 | 129 |
|
224 |
-// SecurityCheckContext validates that the SELinux label is understood by the kernel |
|
225 |
-func SecurityCheckContext(val string) error { |
|
130 |
+func securityCheckContext(val string) error { |
|
226 | 131 |
return nil |
227 | 132 |
} |
228 | 133 |
|
229 |
-/* |
|
230 |
-CopyLevel returns a label with the MLS/MCS level from src label replaced on |
|
231 |
-the dest label. |
|
232 |
-*/ |
|
233 |
-func CopyLevel(src, dest string) (string, error) { |
|
134 |
+func copyLevel(src, dest string) (string, error) { |
|
234 | 135 |
return "", nil |
235 | 136 |
} |
236 | 137 |
|
237 |
-// Chcon changes the `fpath` file object to the SELinux label `label`. |
|
238 |
-// If `fpath` is a directory and `recurse`` is true, Chcon will walk the |
|
239 |
-// directory tree setting the label. |
|
240 |
-func Chcon(fpath string, label string, recurse bool) error { |
|
138 |
+func chcon(fpath string, label string, recurse bool) error { |
|
241 | 139 |
return nil |
242 | 140 |
} |
243 | 141 |
|
244 |
-// DupSecOpt takes an SELinux process label and returns security options that |
|
245 |
-// can be used to set the SELinux Type and Level for future container processes. |
|
246 |
-func DupSecOpt(src string) ([]string, error) { |
|
142 |
+func dupSecOpt(src string) ([]string, error) { |
|
247 | 143 |
return nil, nil |
248 | 144 |
} |
249 | 145 |
|
250 |
-// DisableSecOpt returns a security opt that can be used to disable SELinux |
|
251 |
-// labeling support for future container processes. |
|
252 |
-func DisableSecOpt() []string { |
|
146 |
+func disableSecOpt() []string { |
|
253 | 147 |
return []string{"disable"} |
254 | 148 |
} |
... | ... |
@@ -48,7 +48,11 @@ func WalkN(root string, walkFn WalkFunc, num int) error { |
48 | 48 |
errCh := make(chan error, 1) // get the first error, ignore others |
49 | 49 |
|
50 | 50 |
// Start walking a tree asap |
51 |
- var err error |
|
51 |
+ var ( |
|
52 |
+ err error |
|
53 |
+ wg sync.WaitGroup |
|
54 |
+ ) |
|
55 |
+ wg.Add(1) |
|
52 | 56 |
go func() { |
53 | 57 |
err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error { |
54 | 58 |
if err != nil { |
... | ... |
@@ -68,9 +72,9 @@ func WalkN(root string, walkFn WalkFunc, num int) error { |
68 | 68 |
if err == nil { |
69 | 69 |
close(files) |
70 | 70 |
} |
71 |
+ wg.Done() |
|
71 | 72 |
}() |
72 | 73 |
|
73 |
- var wg sync.WaitGroup |
|
74 | 74 |
wg.Add(num) |
75 | 75 |
for i := 0; i < num; i++ { |
76 | 76 |
go func() { |
77 | 77 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,27 @@ |
0 |
+Copyright (c) 2014 Will Fitzgerald. All rights reserved. |
|
1 |
+ |
|
2 |
+Redistribution and use in source and binary forms, with or without |
|
3 |
+modification, are permitted provided that the following conditions are |
|
4 |
+met: |
|
5 |
+ |
|
6 |
+ * Redistributions of source code must retain the above copyright |
|
7 |
+notice, this list of conditions and the following disclaimer. |
|
8 |
+ * Redistributions in binary form must reproduce the above |
|
9 |
+copyright notice, this list of conditions and the following disclaimer |
|
10 |
+in the documentation and/or other materials provided with the |
|
11 |
+distribution. |
|
12 |
+ * Neither the name of Google Inc. nor the names of its |
|
13 |
+contributors may be used to endorse or promote products derived from |
|
14 |
+this software without specific prior written permission. |
|
15 |
+ |
|
16 |
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
17 |
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
18 |
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
19 |
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
20 |
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
23 |
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
24 |
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
25 |
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
26 |
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
0 | 27 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,94 @@ |
0 |
+# bitset |
|
1 |
+ |
|
2 |
+*Go language library to map between non-negative integers and boolean values* |
|
3 |
+ |
|
4 |
+[![Test](https://github.com/willf/bitset/workflows/Test/badge.svg)](https://github.com/willf/bitset/actions?query=workflow%3ATest) |
|
5 |
+[![Master Coverage Status](https://coveralls.io/repos/willf/bitset/badge.svg?branch=master&service=github)](https://coveralls.io/github/willf/bitset?branch=master) |
|
6 |
+[![Go Report Card](https://goreportcard.com/badge/github.com/willf/bitset)](https://goreportcard.com/report/github.com/willf/bitset) |
|
7 |
+[![PkgGoDev](https://pkg.go.dev/badge/github.com/willf/bitset?tab=doc)](https://pkg.go.dev/github.com/willf/bitset?tab=doc) |
|
8 |
+ |
|
9 |
+ |
|
10 |
+## Description |
|
11 |
+ |
|
12 |
+Package bitset implements bitsets, a mapping between non-negative integers and boolean values. |
|
13 |
+It should be more efficient than map[uint] bool. |
|
14 |
+ |
|
15 |
+It provides methods for setting, clearing, flipping, and testing individual integers. |
|
16 |
+ |
|
17 |
+But it also provides set intersection, union, difference, complement, and symmetric operations, as well as tests to check whether any, all, or no bits are set, and querying a bitset's current length and number of positive bits. |
|
18 |
+ |
|
19 |
+BitSets are expanded to the size of the largest set bit; the memory allocation is approximately Max bits, where Max is the largest set bit. BitSets are never shrunk. On creation, a hint can be given for the number of bits that will be used. |
|
20 |
+ |
|
21 |
+Many of the methods, including Set, Clear, and Flip, return a BitSet pointer, which allows for chaining. |
|
22 |
+ |
|
23 |
+### Example use: |
|
24 |
+ |
|
25 |
+```go |
|
26 |
+package main |
|
27 |
+ |
|
28 |
+import ( |
|
29 |
+ "fmt" |
|
30 |
+ "math/rand" |
|
31 |
+ |
|
32 |
+ "github.com/willf/bitset" |
|
33 |
+) |
|
34 |
+ |
|
35 |
+func main() { |
|
36 |
+ fmt.Printf("Hello from BitSet!\n") |
|
37 |
+ var b bitset.BitSet |
|
38 |
+ // play some Go Fish |
|
39 |
+ for i := 0; i < 100; i++ { |
|
40 |
+ card1 := uint(rand.Intn(52)) |
|
41 |
+ card2 := uint(rand.Intn(52)) |
|
42 |
+ b.Set(card1) |
|
43 |
+ if b.Test(card2) { |
|
44 |
+ fmt.Println("Go Fish!") |
|
45 |
+ } |
|
46 |
+ b.Clear(card1) |
|
47 |
+ } |
|
48 |
+ |
|
49 |
+ // Chaining |
|
50 |
+ b.Set(10).Set(11) |
|
51 |
+ |
|
52 |
+ for i, e := b.NextSet(0); e; i, e = b.NextSet(i + 1) { |
|
53 |
+ fmt.Println("The following bit is set:", i) |
|
54 |
+ } |
|
55 |
+ if b.Intersection(bitset.New(100).Set(10)).Count() == 1 { |
|
56 |
+ fmt.Println("Intersection works.") |
|
57 |
+ } else { |
|
58 |
+ fmt.Println("Intersection doesn't work???") |
|
59 |
+ } |
|
60 |
+} |
|
61 |
+``` |
|
62 |
+ |
|
63 |
+As an alternative to BitSets, one should check out the 'big' package, which provides a (less set-theoretical) view of bitsets. |
|
64 |
+ |
|
65 |
+Package documentation is at: https://pkg.go.dev/github.com/willf/bitset?tab=doc |
|
66 |
+ |
|
67 |
+## Memory Usage |
|
68 |
+ |
|
69 |
+The memory usage of a bitset using N bits is at least N/8 bytes. The number of bits in a bitset is at least as large as one plus the greatest bit index you have accessed. Thus it is possible to run out of memory while using a bitset. If you have lots of bits, you might prefer compressed bitsets, like the [Roaring bitmaps](http://roaringbitmap.org) and its [Go implementation](https://github.com/RoaringBitmap/roaring). |
|
70 |
+ |
|
71 |
+## Implementation Note |
|
72 |
+ |
|
73 |
+Go 1.9 introduced a native `math/bits` library. We provide backward compatibility to Go 1.7, which might be removed. |
|
74 |
+ |
|
75 |
+It is possible that a later version will match the `math/bits` return signature for counts (which is `int`, rather than our library's `unit64`). If so, the version will be bumped. |
|
76 |
+ |
|
77 |
+## Installation |
|
78 |
+ |
|
79 |
+```bash |
|
80 |
+go get github.com/willf/bitset |
|
81 |
+``` |
|
82 |
+ |
|
83 |
+## Contributing |
|
84 |
+ |
|
85 |
+If you wish to contribute to this project, please branch and issue a pull request against master ("[GitHub Flow](https://guides.github.com/introduction/flow/)") |
|
86 |
+ |
|
87 |
+## Running all tests |
|
88 |
+ |
|
89 |
+Before committing the code, please check if it passes tests, has adequate coverage, etc. |
|
90 |
+```bash |
|
91 |
+go test |
|
92 |
+go test -cover |
|
93 |
+``` |
0 | 94 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,931 @@ |
0 |
+/* |
|
1 |
+Package bitset implements bitsets, a mapping |
|
2 |
+between non-negative integers and boolean values. It should be more |
|
3 |
+efficient than map[uint] bool. |
|
4 |
+ |
|
5 |
+It provides methods for setting, clearing, flipping, and testing |
|
6 |
+individual integers. |
|
7 |
+ |
|
8 |
+But it also provides set intersection, union, difference, |
|
9 |
+complement, and symmetric operations, as well as tests to |
|
10 |
+check whether any, all, or no bits are set, and querying a |
|
11 |
+bitset's current length and number of positive bits. |
|
12 |
+ |
|
13 |
+BitSets are expanded to the size of the largest set bit; the |
|
14 |
+memory allocation is approximately Max bits, where Max is |
|
15 |
+the largest set bit. BitSets are never shrunk. On creation, |
|
16 |
+a hint can be given for the number of bits that will be used. |
|
17 |
+ |
|
18 |
+Many of the methods, including Set,Clear, and Flip, return |
|
19 |
+a BitSet pointer, which allows for chaining. |
|
20 |
+ |
|
21 |
+Example use: |
|
22 |
+ |
|
23 |
+ import "bitset" |
|
24 |
+ var b BitSet |
|
25 |
+ b.Set(10).Set(11) |
|
26 |
+ if b.Test(1000) { |
|
27 |
+ b.Clear(1000) |
|
28 |
+ } |
|
29 |
+ if B.Intersection(bitset.New(100).Set(10)).Count() > 1 { |
|
30 |
+ fmt.Println("Intersection works.") |
|
31 |
+ } |
|
32 |
+ |
|
33 |
+As an alternative to BitSets, one should check out the 'big' package, |
|
34 |
+which provides a (less set-theoretical) view of bitsets. |
|
35 |
+ |
|
36 |
+*/ |
|
37 |
+package bitset |
|
38 |
+ |
|
39 |
+import ( |
|
40 |
+ "bufio" |
|
41 |
+ "bytes" |
|
42 |
+ "encoding/base64" |
|
43 |
+ "encoding/binary" |
|
44 |
+ "encoding/json" |
|
45 |
+ "errors" |
|
46 |
+ "fmt" |
|
47 |
+ "io" |
|
48 |
+ "strconv" |
|
49 |
+) |
|
50 |
+ |
|
51 |
+// the wordSize of a bit set |
|
52 |
+const wordSize = uint(64) |
|
53 |
+ |
|
54 |
+// log2WordSize is lg(wordSize) |
|
55 |
+const log2WordSize = uint(6) |
|
56 |
+ |
|
57 |
+// allBits has every bit set |
|
58 |
+const allBits uint64 = 0xffffffffffffffff |
|
59 |
+ |
|
60 |
+// default binary BigEndian |
|
61 |
+var binaryOrder binary.ByteOrder = binary.BigEndian |
|
62 |
+ |
|
63 |
+// default json encoding base64.URLEncoding |
|
64 |
+var base64Encoding = base64.URLEncoding |
|
65 |
+ |
|
66 |
+// Base64StdEncoding Marshal/Unmarshal BitSet with base64.StdEncoding(Default: base64.URLEncoding) |
|
67 |
+func Base64StdEncoding() { base64Encoding = base64.StdEncoding } |
|
68 |
+ |
|
69 |
+// LittleEndian Marshal/Unmarshal Binary as Little Endian(Default: binary.BigEndian) |
|
70 |
+func LittleEndian() { binaryOrder = binary.LittleEndian } |
|
71 |
+ |
|
72 |
+// A BitSet is a set of bits. The zero value of a BitSet is an empty set of length 0. |
|
73 |
+type BitSet struct { |
|
74 |
+ length uint |
|
75 |
+ set []uint64 |
|
76 |
+} |
|
77 |
+ |
|
78 |
+// Error is used to distinguish errors (panics) generated in this package. |
|
79 |
+type Error string |
|
80 |
+ |
|
81 |
+// safeSet will fixup b.set to be non-nil and return the field value |
|
82 |
+func (b *BitSet) safeSet() []uint64 { |
|
83 |
+ if b.set == nil { |
|
84 |
+ b.set = make([]uint64, wordsNeeded(0)) |
|
85 |
+ } |
|
86 |
+ return b.set |
|
87 |
+} |
|
88 |
+ |
|
89 |
+// From is a constructor used to create a BitSet from an array of integers |
|
90 |
+func From(buf []uint64) *BitSet { |
|
91 |
+ return &BitSet{uint(len(buf)) * 64, buf} |
|
92 |
+} |
|
93 |
+ |
|
94 |
+// Bytes returns the bitset as array of integers |
|
95 |
+func (b *BitSet) Bytes() []uint64 { |
|
96 |
+ return b.set |
|
97 |
+} |
|
98 |
+ |
|
99 |
+// wordsNeeded calculates the number of words needed for i bits |
|
100 |
+func wordsNeeded(i uint) int { |
|
101 |
+ if i > (Cap() - wordSize + 1) { |
|
102 |
+ return int(Cap() >> log2WordSize) |
|
103 |
+ } |
|
104 |
+ return int((i + (wordSize - 1)) >> log2WordSize) |
|
105 |
+} |
|
106 |
+ |
|
107 |
+// New creates a new BitSet with a hint that length bits will be required |
|
108 |
+func New(length uint) (bset *BitSet) { |
|
109 |
+ defer func() { |
|
110 |
+ if r := recover(); r != nil { |
|
111 |
+ bset = &BitSet{ |
|
112 |
+ 0, |
|
113 |
+ make([]uint64, 0), |
|
114 |
+ } |
|
115 |
+ } |
|
116 |
+ }() |
|
117 |
+ |
|
118 |
+ bset = &BitSet{ |
|
119 |
+ length, |
|
120 |
+ make([]uint64, wordsNeeded(length)), |
|
121 |
+ } |
|
122 |
+ |
|
123 |
+ return bset |
|
124 |
+} |
|
125 |
+ |
|
126 |
+// Cap returns the total possible capacity, or number of bits |
|
127 |
+func Cap() uint { |
|
128 |
+ return ^uint(0) |
|
129 |
+} |
|
130 |
+ |
|
131 |
+// Len returns the number of bits in the BitSet. |
|
132 |
+// Note the difference to method Count, see example. |
|
133 |
+func (b *BitSet) Len() uint { |
|
134 |
+ return b.length |
|
135 |
+} |
|
136 |
+ |
|
137 |
+// extendSetMaybe adds additional words to incorporate new bits if needed |
|
138 |
+func (b *BitSet) extendSetMaybe(i uint) { |
|
139 |
+ if i >= b.length { // if we need more bits, make 'em |
|
140 |
+ if i >= Cap() { |
|
141 |
+ panic("You are exceeding the capacity") |
|
142 |
+ } |
|
143 |
+ nsize := wordsNeeded(i + 1) |
|
144 |
+ if b.set == nil { |
|
145 |
+ b.set = make([]uint64, nsize) |
|
146 |
+ } else if cap(b.set) >= nsize { |
|
147 |
+ b.set = b.set[:nsize] // fast resize |
|
148 |
+ } else if len(b.set) < nsize { |
|
149 |
+ newset := make([]uint64, nsize, 2*nsize) // increase capacity 2x |
|
150 |
+ copy(newset, b.set) |
|
151 |
+ b.set = newset |
|
152 |
+ } |
|
153 |
+ b.length = i + 1 |
|
154 |
+ } |
|
155 |
+} |
|
156 |
+ |
|
157 |
+// Test whether bit i is set. |
|
158 |
+func (b *BitSet) Test(i uint) bool { |
|
159 |
+ if i >= b.length { |
|
160 |
+ return false |
|
161 |
+ } |
|
162 |
+ return b.set[i>>log2WordSize]&(1<<(i&(wordSize-1))) != 0 |
|
163 |
+} |
|
164 |
+ |
|
165 |
+// Set bit i to 1, the capacity of the bitset is automatically |
|
166 |
+// increased accordingly. |
|
167 |
+// If i>= Cap(), this function will panic. |
|
168 |
+// Warning: using a very large value for 'i' |
|
169 |
+// may lead to a memory shortage and a panic: the caller is responsible |
|
170 |
+// for providing sensible parameters in line with their memory capacity. |
|
171 |
+func (b *BitSet) Set(i uint) *BitSet { |
|
172 |
+ b.extendSetMaybe(i) |
|
173 |
+ b.set[i>>log2WordSize] |= 1 << (i & (wordSize - 1)) |
|
174 |
+ return b |
|
175 |
+} |
|
176 |
+ |
|
177 |
+// Clear bit i to 0 |
|
178 |
+func (b *BitSet) Clear(i uint) *BitSet { |
|
179 |
+ if i >= b.length { |
|
180 |
+ return b |
|
181 |
+ } |
|
182 |
+ b.set[i>>log2WordSize] &^= 1 << (i & (wordSize - 1)) |
|
183 |
+ return b |
|
184 |
+} |
|
185 |
+ |
|
186 |
+// SetTo sets bit i to value. |
|
187 |
+// If i>= Cap(), this function will panic. |
|
188 |
+// Warning: using a very large value for 'i' |
|
189 |
+// may lead to a memory shortage and a panic: the caller is responsible |
|
190 |
+// for providing sensible parameters in line with their memory capacity. |
|
191 |
+func (b *BitSet) SetTo(i uint, value bool) *BitSet { |
|
192 |
+ if value { |
|
193 |
+ return b.Set(i) |
|
194 |
+ } |
|
195 |
+ return b.Clear(i) |
|
196 |
+} |
|
197 |
+ |
|
198 |
+// Flip bit at i. |
|
199 |
+// If i>= Cap(), this function will panic. |
|
200 |
+// Warning: using a very large value for 'i' |
|
201 |
+// may lead to a memory shortage and a panic: the caller is responsible |
|
202 |
+// for providing sensible parameters in line with their memory capacity. |
|
203 |
+func (b *BitSet) Flip(i uint) *BitSet { |
|
204 |
+ if i >= b.length { |
|
205 |
+ return b.Set(i) |
|
206 |
+ } |
|
207 |
+ b.set[i>>log2WordSize] ^= 1 << (i & (wordSize - 1)) |
|
208 |
+ return b |
|
209 |
+} |
|
210 |
+ |
|
211 |
+// Shrink shrinks BitSet so that the provided value is the last possible |
|
212 |
+// set value. It clears all bits > the provided index and reduces the size |
|
213 |
+// and length of the set. |
|
214 |
+// |
|
215 |
+// Note that the parameter value is not the new length in bits: it is the |
|
216 |
+// maximal value that can be stored in the bitset after the function call. |
|
217 |
+// The new length in bits is the parameter value + 1. Thus it is not possible |
|
218 |
+// to use this function to set the length to 0, the minimal value of the length |
|
219 |
+// after this function call is 1. |
|
220 |
+// |
|
221 |
+// A new slice is allocated to store the new bits, so you may see an increase in |
|
222 |
+// memory usage until the GC runs. Normally this should not be a problem, but if you |
|
223 |
+// have an extremely large BitSet its important to understand that the old BitSet will |
|
224 |
+// remain in memory until the GC frees it. |
|
225 |
+func (b *BitSet) Shrink(lastbitindex uint) *BitSet { |
|
226 |
+ length := lastbitindex + 1 |
|
227 |
+ idx := wordsNeeded(length) |
|
228 |
+ if idx > len(b.set) { |
|
229 |
+ return b |
|
230 |
+ } |
|
231 |
+ shrunk := make([]uint64, idx) |
|
232 |
+ copy(shrunk, b.set[:idx]) |
|
233 |
+ b.set = shrunk |
|
234 |
+ b.length = length |
|
235 |
+ b.set[idx-1] &= (allBits >> (uint64(64) - uint64(length&(wordSize-1)))) |
|
236 |
+ return b |
|
237 |
+} |
|
238 |
+ |
|
239 |
+// Compact shrinks BitSet to so that we preserve all set bits, while minimizing |
|
240 |
+// memory usage. Compact calls Shrink. |
|
241 |
+func (b *BitSet) Compact() *BitSet { |
|
242 |
+ idx := len(b.set) - 1 |
|
243 |
+ for ; idx >= 0 && b.set[idx] == 0; idx-- { |
|
244 |
+ } |
|
245 |
+ newlength := uint((idx + 1) << log2WordSize) |
|
246 |
+ if newlength >= b.length { |
|
247 |
+ return b // nothing to do |
|
248 |
+ } |
|
249 |
+ if newlength > 0 { |
|
250 |
+ return b.Shrink(newlength - 1) |
|
251 |
+ } |
|
252 |
+ // We preserve one word |
|
253 |
+ return b.Shrink(63) |
|
254 |
+} |
|
255 |
+ |
|
256 |
+// InsertAt takes an index which indicates where a bit should be |
|
257 |
+// inserted. Then it shifts all the bits in the set to the left by 1, starting |
|
258 |
+// from the given index position, and sets the index position to 0. |
|
259 |
+// |
|
260 |
+// Depending on the size of your BitSet, and where you are inserting the new entry, |
|
261 |
+// this method could be extremely slow and in some cases might cause the entire BitSet |
|
262 |
+// to be recopied. |
|
263 |
+func (b *BitSet) InsertAt(idx uint) *BitSet { |
|
264 |
+ insertAtElement := (idx >> log2WordSize) |
|
265 |
+ |
|
266 |
+ // if length of set is a multiple of wordSize we need to allocate more space first |
|
267 |
+ if b.isLenExactMultiple() { |
|
268 |
+ b.set = append(b.set, uint64(0)) |
|
269 |
+ } |
|
270 |
+ |
|
271 |
+ var i uint |
|
272 |
+ for i = uint(len(b.set) - 1); i > insertAtElement; i-- { |
|
273 |
+ // all elements above the position where we want to insert can simply by shifted |
|
274 |
+ b.set[i] <<= 1 |
|
275 |
+ |
|
276 |
+ // we take the most significant bit of the previous element and set it as |
|
277 |
+ // the least significant bit of the current element |
|
278 |
+ b.set[i] |= (b.set[i-1] & 0x8000000000000000) >> 63 |
|
279 |
+ } |
|
280 |
+ |
|
281 |
+ // generate a mask to extract the data that we need to shift left |
|
282 |
+ // within the element where we insert a bit |
|
283 |
+ dataMask := ^(uint64(1)<<uint64(idx&(wordSize-1)) - 1) |
|
284 |
+ |
|
285 |
+ // extract that data that we'll shift |
|
286 |
+ data := b.set[i] & dataMask |
|
287 |
+ |
|
288 |
+ // set the positions of the data mask to 0 in the element where we insert |
|
289 |
+ b.set[i] &= ^dataMask |
|
290 |
+ |
|
291 |
+ // shift data mask to the left and insert its data to the slice element |
|
292 |
+ b.set[i] |= data << 1 |
|
293 |
+ |
|
294 |
+ // add 1 to length of BitSet |
|
295 |
+ b.length++ |
|
296 |
+ |
|
297 |
+ return b |
|
298 |
+} |
|
299 |
+ |
|
300 |
+// String creates a string representation of the Bitmap |
|
301 |
+func (b *BitSet) String() string { |
|
302 |
+ // follows code from https://github.com/RoaringBitmap/roaring |
|
303 |
+ var buffer bytes.Buffer |
|
304 |
+ start := []byte("{") |
|
305 |
+ buffer.Write(start) |
|
306 |
+ counter := 0 |
|
307 |
+ i, e := b.NextSet(0) |
|
308 |
+ for e { |
|
309 |
+ counter = counter + 1 |
|
310 |
+ // to avoid exhausting the memory |
|
311 |
+ if counter > 0x40000 { |
|
312 |
+ buffer.WriteString("...") |
|
313 |
+ break |
|
314 |
+ } |
|
315 |
+ buffer.WriteString(strconv.FormatInt(int64(i), 10)) |
|
316 |
+ i, e = b.NextSet(i + 1) |
|
317 |
+ if e { |
|
318 |
+ buffer.WriteString(",") |
|
319 |
+ } |
|
320 |
+ } |
|
321 |
+ buffer.WriteString("}") |
|
322 |
+ return buffer.String() |
|
323 |
+} |
|
324 |
+ |
|
325 |
+// DeleteAt deletes the bit at the given index position from |
|
326 |
+// within the bitset |
|
327 |
+// All the bits residing on the left of the deleted bit get |
|
328 |
+// shifted right by 1 |
|
329 |
+// The running time of this operation may potentially be |
|
330 |
+// relatively slow, O(length) |
|
331 |
+func (b *BitSet) DeleteAt(i uint) *BitSet { |
|
332 |
+ // the index of the slice element where we'll delete a bit |
|
333 |
+ deleteAtElement := i >> log2WordSize |
|
334 |
+ |
|
335 |
+ // generate a mask for the data that needs to be shifted right |
|
336 |
+ // within that slice element that gets modified |
|
337 |
+ dataMask := ^((uint64(1) << (i & (wordSize - 1))) - 1) |
|
338 |
+ |
|
339 |
+ // extract the data that we'll shift right from the slice element |
|
340 |
+ data := b.set[deleteAtElement] & dataMask |
|
341 |
+ |
|
342 |
+ // set the masked area to 0 while leaving the rest as it is |
|
343 |
+ b.set[deleteAtElement] &= ^dataMask |
|
344 |
+ |
|
345 |
+ // shift the previously extracted data to the right and then |
|
346 |
+ // set it in the previously masked area |
|
347 |
+ b.set[deleteAtElement] |= (data >> 1) & dataMask |
|
348 |
+ |
|
349 |
+ // loop over all the consecutive slice elements to copy each |
|
350 |
+ // lowest bit into the highest position of the previous element, |
|
351 |
+ // then shift the entire content to the right by 1 |
|
352 |
+ for i := int(deleteAtElement) + 1; i < len(b.set); i++ { |
|
353 |
+ b.set[i-1] |= (b.set[i] & 1) << 63 |
|
354 |
+ b.set[i] >>= 1 |
|
355 |
+ } |
|
356 |
+ |
|
357 |
+ b.length = b.length - 1 |
|
358 |
+ |
|
359 |
+ return b |
|
360 |
+} |
|
361 |
+ |
|
362 |
+// NextSet returns the next bit set from the specified index, |
|
363 |
+// including possibly the current index |
|
364 |
+// along with an error code (true = valid, false = no set bit found) |
|
365 |
+// for i,e := v.NextSet(0); e; i,e = v.NextSet(i + 1) {...} |
|
366 |
+// |
|
367 |
+// Users concerned with performance may want to use NextSetMany to |
|
368 |
+// retrieve several values at once. |
|
369 |
+func (b *BitSet) NextSet(i uint) (uint, bool) { |
|
370 |
+ x := int(i >> log2WordSize) |
|
371 |
+ if x >= len(b.set) { |
|
372 |
+ return 0, false |
|
373 |
+ } |
|
374 |
+ w := b.set[x] |
|
375 |
+ w = w >> (i & (wordSize - 1)) |
|
376 |
+ if w != 0 { |
|
377 |
+ return i + trailingZeroes64(w), true |
|
378 |
+ } |
|
379 |
+ x = x + 1 |
|
380 |
+ for x < len(b.set) { |
|
381 |
+ if b.set[x] != 0 { |
|
382 |
+ return uint(x)*wordSize + trailingZeroes64(b.set[x]), true |
|
383 |
+ } |
|
384 |
+ x = x + 1 |
|
385 |
+ |
|
386 |
+ } |
|
387 |
+ return 0, false |
|
388 |
+} |
|
389 |
+ |
|
390 |
+// NextSetMany returns many next bit sets from the specified index, |
|
391 |
+// including possibly the current index and up to cap(buffer). |
|
392 |
+// If the returned slice has len zero, then no more set bits were found |
|
393 |
+// |
|
394 |
+// buffer := make([]uint, 256) // this should be reused |
|
395 |
+// j := uint(0) |
|
396 |
+// j, buffer = bitmap.NextSetMany(j, buffer) |
|
397 |
+// for ; len(buffer) > 0; j, buffer = bitmap.NextSetMany(j,buffer) { |
|
398 |
+// for k := range buffer { |
|
399 |
+// do something with buffer[k] |
|
400 |
+// } |
|
401 |
+// j += 1 |
|
402 |
+// } |
|
403 |
+// |
|
404 |
+// |
|
405 |
+// It is possible to retrieve all set bits as follow: |
|
406 |
+// |
|
407 |
+// indices := make([]uint, bitmap.Count()) |
|
408 |
+// bitmap.NextSetMany(0, indices) |
|
409 |
+// |
|
410 |
+// However if bitmap.Count() is large, it might be preferable to |
|
411 |
+// use several calls to NextSetMany, for performance reasons. |
|
412 |
+func (b *BitSet) NextSetMany(i uint, buffer []uint) (uint, []uint) { |
|
413 |
+ myanswer := buffer |
|
414 |
+ capacity := cap(buffer) |
|
415 |
+ x := int(i >> log2WordSize) |
|
416 |
+ if x >= len(b.set) || capacity == 0 { |
|
417 |
+ return 0, myanswer[:0] |
|
418 |
+ } |
|
419 |
+ skip := i & (wordSize - 1) |
|
420 |
+ word := b.set[x] >> skip |
|
421 |
+ myanswer = myanswer[:capacity] |
|
422 |
+ size := int(0) |
|
423 |
+ for word != 0 { |
|
424 |
+ r := trailingZeroes64(word) |
|
425 |
+ t := word & ((^word) + 1) |
|
426 |
+ myanswer[size] = r + i |
|
427 |
+ size++ |
|
428 |
+ if size == capacity { |
|
429 |
+ goto End |
|
430 |
+ } |
|
431 |
+ word = word ^ t |
|
432 |
+ } |
|
433 |
+ x++ |
|
434 |
+ for idx, word := range b.set[x:] { |
|
435 |
+ for word != 0 { |
|
436 |
+ r := trailingZeroes64(word) |
|
437 |
+ t := word & ((^word) + 1) |
|
438 |
+ myanswer[size] = r + (uint(x+idx) << 6) |
|
439 |
+ size++ |
|
440 |
+ if size == capacity { |
|
441 |
+ goto End |
|
442 |
+ } |
|
443 |
+ word = word ^ t |
|
444 |
+ } |
|
445 |
+ } |
|
446 |
+End: |
|
447 |
+ if size > 0 { |
|
448 |
+ return myanswer[size-1], myanswer[:size] |
|
449 |
+ } |
|
450 |
+ return 0, myanswer[:0] |
|
451 |
+} |
|
452 |
+ |
|
453 |
+// NextClear returns the next clear bit from the specified index, |
|
454 |
+// including possibly the current index |
|
455 |
+// along with an error code (true = valid, false = no bit found i.e. all bits are set) |
|
456 |
+func (b *BitSet) NextClear(i uint) (uint, bool) { |
|
457 |
+ x := int(i >> log2WordSize) |
|
458 |
+ if x >= len(b.set) { |
|
459 |
+ return 0, false |
|
460 |
+ } |
|
461 |
+ w := b.set[x] |
|
462 |
+ w = w >> (i & (wordSize - 1)) |
|
463 |
+ wA := allBits >> (i & (wordSize - 1)) |
|
464 |
+ index := i + trailingZeroes64(^w) |
|
465 |
+ if w != wA && index < b.length { |
|
466 |
+ return index, true |
|
467 |
+ } |
|
468 |
+ x++ |
|
469 |
+ for x < len(b.set) { |
|
470 |
+ index = uint(x)*wordSize + trailingZeroes64(^b.set[x]) |
|
471 |
+ if b.set[x] != allBits && index < b.length { |
|
472 |
+ return index, true |
|
473 |
+ } |
|
474 |
+ x++ |
|
475 |
+ } |
|
476 |
+ return 0, false |
|
477 |
+} |
|
478 |
+ |
|
479 |
+// ClearAll clears the entire BitSet |
|
480 |
+func (b *BitSet) ClearAll() *BitSet { |
|
481 |
+ if b != nil && b.set != nil { |
|
482 |
+ for i := range b.set { |
|
483 |
+ b.set[i] = 0 |
|
484 |
+ } |
|
485 |
+ } |
|
486 |
+ return b |
|
487 |
+} |
|
488 |
+ |
|
489 |
+// wordCount returns the number of words used in a bit set |
|
490 |
+func (b *BitSet) wordCount() int { |
|
491 |
+ return len(b.set) |
|
492 |
+} |
|
493 |
+ |
|
494 |
+// Clone this BitSet |
|
495 |
+func (b *BitSet) Clone() *BitSet { |
|
496 |
+ c := New(b.length) |
|
497 |
+ if b.set != nil { // Clone should not modify current object |
|
498 |
+ copy(c.set, b.set) |
|
499 |
+ } |
|
500 |
+ return c |
|
501 |
+} |
|
502 |
+ |
|
503 |
+// Copy into a destination BitSet |
|
504 |
+// Returning the size of the destination BitSet |
|
505 |
+// like array copy |
|
506 |
+func (b *BitSet) Copy(c *BitSet) (count uint) { |
|
507 |
+ if c == nil { |
|
508 |
+ return |
|
509 |
+ } |
|
510 |
+ if b.set != nil { // Copy should not modify current object |
|
511 |
+ copy(c.set, b.set) |
|
512 |
+ } |
|
513 |
+ count = c.length |
|
514 |
+ if b.length < c.length { |
|
515 |
+ count = b.length |
|
516 |
+ } |
|
517 |
+ return |
|
518 |
+} |
|
519 |
+ |
|
520 |
+// Count (number of set bits). |
|
521 |
+// Also known as "popcount" or "popularity count". |
|
522 |
+func (b *BitSet) Count() uint { |
|
523 |
+ if b != nil && b.set != nil { |
|
524 |
+ return uint(popcntSlice(b.set)) |
|
525 |
+ } |
|
526 |
+ return 0 |
|
527 |
+} |
|
528 |
+ |
|
529 |
+// Equal tests the equivalence of two BitSets. |
|
530 |
+// False if they are of different sizes, otherwise true |
|
531 |
+// only if all the same bits are set |
|
532 |
+func (b *BitSet) Equal(c *BitSet) bool { |
|
533 |
+ if c == nil || b == nil { |
|
534 |
+ return c == b |
|
535 |
+ } |
|
536 |
+ if b.length != c.length { |
|
537 |
+ return false |
|
538 |
+ } |
|
539 |
+ if b.length == 0 { // if they have both length == 0, then could have nil set |
|
540 |
+ return true |
|
541 |
+ } |
|
542 |
+ // testing for equality shoud not transform the bitset (no call to safeSet) |
|
543 |
+ |
|
544 |
+ for p, v := range b.set { |
|
545 |
+ if c.set[p] != v { |
|
546 |
+ return false |
|
547 |
+ } |
|
548 |
+ } |
|
549 |
+ return true |
|
550 |
+} |
|
551 |
+ |
|
552 |
+func panicIfNull(b *BitSet) { |
|
553 |
+ if b == nil { |
|
554 |
+ panic(Error("BitSet must not be null")) |
|
555 |
+ } |
|
556 |
+} |
|
557 |
+ |
|
558 |
+// Difference of base set and other set |
|
559 |
+// This is the BitSet equivalent of &^ (and not) |
|
560 |
+func (b *BitSet) Difference(compare *BitSet) (result *BitSet) { |
|
561 |
+ panicIfNull(b) |
|
562 |
+ panicIfNull(compare) |
|
563 |
+ result = b.Clone() // clone b (in case b is bigger than compare) |
|
564 |
+ l := int(compare.wordCount()) |
|
565 |
+ if l > int(b.wordCount()) { |
|
566 |
+ l = int(b.wordCount()) |
|
567 |
+ } |
|
568 |
+ for i := 0; i < l; i++ { |
|
569 |
+ result.set[i] = b.set[i] &^ compare.set[i] |
|
570 |
+ } |
|
571 |
+ return |
|
572 |
+} |
|
573 |
+ |
|
574 |
+// DifferenceCardinality computes the cardinality of the differnce |
|
575 |
+func (b *BitSet) DifferenceCardinality(compare *BitSet) uint { |
|
576 |
+ panicIfNull(b) |
|
577 |
+ panicIfNull(compare) |
|
578 |
+ l := int(compare.wordCount()) |
|
579 |
+ if l > int(b.wordCount()) { |
|
580 |
+ l = int(b.wordCount()) |
|
581 |
+ } |
|
582 |
+ cnt := uint64(0) |
|
583 |
+ cnt += popcntMaskSlice(b.set[:l], compare.set[:l]) |
|
584 |
+ cnt += popcntSlice(b.set[l:]) |
|
585 |
+ return uint(cnt) |
|
586 |
+} |
|
587 |
+ |
|
588 |
+// InPlaceDifference computes the difference of base set and other set |
|
589 |
+// This is the BitSet equivalent of &^ (and not) |
|
590 |
+func (b *BitSet) InPlaceDifference(compare *BitSet) { |
|
591 |
+ panicIfNull(b) |
|
592 |
+ panicIfNull(compare) |
|
593 |
+ l := int(compare.wordCount()) |
|
594 |
+ if l > int(b.wordCount()) { |
|
595 |
+ l = int(b.wordCount()) |
|
596 |
+ } |
|
597 |
+ for i := 0; i < l; i++ { |
|
598 |
+ b.set[i] &^= compare.set[i] |
|
599 |
+ } |
|
600 |
+} |
|
601 |
+ |
|
602 |
+// Convenience function: return two bitsets ordered by |
|
603 |
+// increasing length. Note: neither can be nil |
|
604 |
+func sortByLength(a *BitSet, b *BitSet) (ap *BitSet, bp *BitSet) { |
|
605 |
+ if a.length <= b.length { |
|
606 |
+ ap, bp = a, b |
|
607 |
+ } else { |
|
608 |
+ ap, bp = b, a |
|
609 |
+ } |
|
610 |
+ return |
|
611 |
+} |
|
612 |
+ |
|
613 |
+// Intersection of base set and other set |
|
614 |
+// This is the BitSet equivalent of & (and) |
|
615 |
+func (b *BitSet) Intersection(compare *BitSet) (result *BitSet) { |
|
616 |
+ panicIfNull(b) |
|
617 |
+ panicIfNull(compare) |
|
618 |
+ b, compare = sortByLength(b, compare) |
|
619 |
+ result = New(b.length) |
|
620 |
+ for i, word := range b.set { |
|
621 |
+ result.set[i] = word & compare.set[i] |
|
622 |
+ } |
|
623 |
+ return |
|
624 |
+} |
|
625 |
+ |
|
626 |
+// IntersectionCardinality computes the cardinality of the union |
|
627 |
+func (b *BitSet) IntersectionCardinality(compare *BitSet) uint { |
|
628 |
+ panicIfNull(b) |
|
629 |
+ panicIfNull(compare) |
|
630 |
+ b, compare = sortByLength(b, compare) |
|
631 |
+ cnt := popcntAndSlice(b.set, compare.set) |
|
632 |
+ return uint(cnt) |
|
633 |
+} |
|
634 |
+ |
|
635 |
+// InPlaceIntersection destructively computes the intersection of |
|
636 |
+// base set and the compare set. |
|
637 |
+// This is the BitSet equivalent of & (and) |
|
638 |
+func (b *BitSet) InPlaceIntersection(compare *BitSet) { |
|
639 |
+ panicIfNull(b) |
|
640 |
+ panicIfNull(compare) |
|
641 |
+ l := int(compare.wordCount()) |
|
642 |
+ if l > int(b.wordCount()) { |
|
643 |
+ l = int(b.wordCount()) |
|
644 |
+ } |
|
645 |
+ for i := 0; i < l; i++ { |
|
646 |
+ b.set[i] &= compare.set[i] |
|
647 |
+ } |
|
648 |
+ for i := l; i < len(b.set); i++ { |
|
649 |
+ b.set[i] = 0 |
|
650 |
+ } |
|
651 |
+ if compare.length > 0 { |
|
652 |
+ b.extendSetMaybe(compare.length - 1) |
|
653 |
+ } |
|
654 |
+} |
|
655 |
+ |
|
656 |
+// Union of base set and other set |
|
657 |
+// This is the BitSet equivalent of | (or) |
|
658 |
+func (b *BitSet) Union(compare *BitSet) (result *BitSet) { |
|
659 |
+ panicIfNull(b) |
|
660 |
+ panicIfNull(compare) |
|
661 |
+ b, compare = sortByLength(b, compare) |
|
662 |
+ result = compare.Clone() |
|
663 |
+ for i, word := range b.set { |
|
664 |
+ result.set[i] = word | compare.set[i] |
|
665 |
+ } |
|
666 |
+ return |
|
667 |
+} |
|
668 |
+ |
|
669 |
+// UnionCardinality computes the cardinality of the uniton of the base set |
|
670 |
+// and the compare set. |
|
671 |
+func (b *BitSet) UnionCardinality(compare *BitSet) uint { |
|
672 |
+ panicIfNull(b) |
|
673 |
+ panicIfNull(compare) |
|
674 |
+ b, compare = sortByLength(b, compare) |
|
675 |
+ cnt := popcntOrSlice(b.set, compare.set) |
|
676 |
+ if len(compare.set) > len(b.set) { |
|
677 |
+ cnt += popcntSlice(compare.set[len(b.set):]) |
|
678 |
+ } |
|
679 |
+ return uint(cnt) |
|
680 |
+} |
|
681 |
+ |
|
682 |
+// InPlaceUnion creates the destructive union of base set and compare set. |
|
683 |
+// This is the BitSet equivalent of | (or). |
|
684 |
+func (b *BitSet) InPlaceUnion(compare *BitSet) { |
|
685 |
+ panicIfNull(b) |
|
686 |
+ panicIfNull(compare) |
|
687 |
+ l := int(compare.wordCount()) |
|
688 |
+ if l > int(b.wordCount()) { |
|
689 |
+ l = int(b.wordCount()) |
|
690 |
+ } |
|
691 |
+ if compare.length > 0 { |
|
692 |
+ b.extendSetMaybe(compare.length - 1) |
|
693 |
+ } |
|
694 |
+ for i := 0; i < l; i++ { |
|
695 |
+ b.set[i] |= compare.set[i] |
|
696 |
+ } |
|
697 |
+ if len(compare.set) > l { |
|
698 |
+ for i := l; i < len(compare.set); i++ { |
|
699 |
+ b.set[i] = compare.set[i] |
|
700 |
+ } |
|
701 |
+ } |
|
702 |
+} |
|
703 |
+ |
|
704 |
+// SymmetricDifference of base set and other set |
|
705 |
+// This is the BitSet equivalent of ^ (xor) |
|
706 |
+func (b *BitSet) SymmetricDifference(compare *BitSet) (result *BitSet) { |
|
707 |
+ panicIfNull(b) |
|
708 |
+ panicIfNull(compare) |
|
709 |
+ b, compare = sortByLength(b, compare) |
|
710 |
+ // compare is bigger, so clone it |
|
711 |
+ result = compare.Clone() |
|
712 |
+ for i, word := range b.set { |
|
713 |
+ result.set[i] = word ^ compare.set[i] |
|
714 |
+ } |
|
715 |
+ return |
|
716 |
+} |
|
717 |
+ |
|
718 |
+// SymmetricDifferenceCardinality computes the cardinality of the symmetric difference |
|
719 |
+func (b *BitSet) SymmetricDifferenceCardinality(compare *BitSet) uint { |
|
720 |
+ panicIfNull(b) |
|
721 |
+ panicIfNull(compare) |
|
722 |
+ b, compare = sortByLength(b, compare) |
|
723 |
+ cnt := popcntXorSlice(b.set, compare.set) |
|
724 |
+ if len(compare.set) > len(b.set) { |
|
725 |
+ cnt += popcntSlice(compare.set[len(b.set):]) |
|
726 |
+ } |
|
727 |
+ return uint(cnt) |
|
728 |
+} |
|
729 |
+ |
|
730 |
+// InPlaceSymmetricDifference creates the destructive SymmetricDifference of base set and other set |
|
731 |
+// This is the BitSet equivalent of ^ (xor) |
|
732 |
+func (b *BitSet) InPlaceSymmetricDifference(compare *BitSet) { |
|
733 |
+ panicIfNull(b) |
|
734 |
+ panicIfNull(compare) |
|
735 |
+ l := int(compare.wordCount()) |
|
736 |
+ if l > int(b.wordCount()) { |
|
737 |
+ l = int(b.wordCount()) |
|
738 |
+ } |
|
739 |
+ if compare.length > 0 { |
|
740 |
+ b.extendSetMaybe(compare.length - 1) |
|
741 |
+ } |
|
742 |
+ for i := 0; i < l; i++ { |
|
743 |
+ b.set[i] ^= compare.set[i] |
|
744 |
+ } |
|
745 |
+ if len(compare.set) > l { |
|
746 |
+ for i := l; i < len(compare.set); i++ { |
|
747 |
+ b.set[i] = compare.set[i] |
|
748 |
+ } |
|
749 |
+ } |
|
750 |
+} |
|
751 |
+ |
|
752 |
+// Is the length an exact multiple of word sizes? |
|
753 |
+func (b *BitSet) isLenExactMultiple() bool { |
|
754 |
+ return b.length%wordSize == 0 |
|
755 |
+} |
|
756 |
+ |
|
757 |
+// Clean last word by setting unused bits to 0 |
|
758 |
+func (b *BitSet) cleanLastWord() { |
|
759 |
+ if !b.isLenExactMultiple() { |
|
760 |
+ b.set[len(b.set)-1] &= allBits >> (wordSize - b.length%wordSize) |
|
761 |
+ } |
|
762 |
+} |
|
763 |
+ |
|
764 |
+// Complement computes the (local) complement of a biset (up to length bits) |
|
765 |
+func (b *BitSet) Complement() (result *BitSet) { |
|
766 |
+ panicIfNull(b) |
|
767 |
+ result = New(b.length) |
|
768 |
+ for i, word := range b.set { |
|
769 |
+ result.set[i] = ^word |
|
770 |
+ } |
|
771 |
+ result.cleanLastWord() |
|
772 |
+ return |
|
773 |
+} |
|
774 |
+ |
|
775 |
+// All returns true if all bits are set, false otherwise. Returns true for |
|
776 |
+// empty sets. |
|
777 |
+func (b *BitSet) All() bool { |
|
778 |
+ panicIfNull(b) |
|
779 |
+ return b.Count() == b.length |
|
780 |
+} |
|
781 |
+ |
|
782 |
+// None returns true if no bit is set, false otherwise. Returns true for |
|
783 |
+// empty sets. |
|
784 |
+func (b *BitSet) None() bool { |
|
785 |
+ panicIfNull(b) |
|
786 |
+ if b != nil && b.set != nil { |
|
787 |
+ for _, word := range b.set { |
|
788 |
+ if word > 0 { |
|
789 |
+ return false |
|
790 |
+ } |
|
791 |
+ } |
|
792 |
+ return true |
|
793 |
+ } |
|
794 |
+ return true |
|
795 |
+} |
|
796 |
+ |
|
797 |
+// Any returns true if any bit is set, false otherwise |
|
798 |
+func (b *BitSet) Any() bool { |
|
799 |
+ panicIfNull(b) |
|
800 |
+ return !b.None() |
|
801 |
+} |
|
802 |
+ |
|
803 |
+// IsSuperSet returns true if this is a superset of the other set |
|
804 |
+func (b *BitSet) IsSuperSet(other *BitSet) bool { |
|
805 |
+ for i, e := other.NextSet(0); e; i, e = other.NextSet(i + 1) { |
|
806 |
+ if !b.Test(i) { |
|
807 |
+ return false |
|
808 |
+ } |
|
809 |
+ } |
|
810 |
+ return true |
|
811 |
+} |
|
812 |
+ |
|
813 |
+// IsStrictSuperSet returns true if this is a strict superset of the other set |
|
814 |
+func (b *BitSet) IsStrictSuperSet(other *BitSet) bool { |
|
815 |
+ return b.Count() > other.Count() && b.IsSuperSet(other) |
|
816 |
+} |
|
817 |
+ |
|
818 |
+// DumpAsBits dumps a bit set as a string of bits |
|
819 |
+func (b *BitSet) DumpAsBits() string { |
|
820 |
+ if b.set == nil { |
|
821 |
+ return "." |
|
822 |
+ } |
|
823 |
+ buffer := bytes.NewBufferString("") |
|
824 |
+ i := len(b.set) - 1 |
|
825 |
+ for ; i >= 0; i-- { |
|
826 |
+ fmt.Fprintf(buffer, "%064b.", b.set[i]) |
|
827 |
+ } |
|
828 |
+ return buffer.String() |
|
829 |
+} |
|
830 |
+ |
|
831 |
+// BinaryStorageSize returns the binary storage requirements |
|
832 |
+func (b *BitSet) BinaryStorageSize() int { |
|
833 |
+ return binary.Size(uint64(0)) + binary.Size(b.set) |
|
834 |
+} |
|
835 |
+ |
|
836 |
+// WriteTo writes a BitSet to a stream |
|
837 |
+func (b *BitSet) WriteTo(stream io.Writer) (int64, error) { |
|
838 |
+ length := uint64(b.length) |
|
839 |
+ |
|
840 |
+ // Write length |
|
841 |
+ err := binary.Write(stream, binaryOrder, length) |
|
842 |
+ if err != nil { |
|
843 |
+ return 0, err |
|
844 |
+ } |
|
845 |
+ |
|
846 |
+ // Write set |
|
847 |
+ err = binary.Write(stream, binaryOrder, b.set) |
|
848 |
+ return int64(b.BinaryStorageSize()), err |
|
849 |
+} |
|
850 |
+ |
|
851 |
+// ReadFrom reads a BitSet from a stream written using WriteTo |
|
852 |
+func (b *BitSet) ReadFrom(stream io.Reader) (int64, error) { |
|
853 |
+ var length uint64 |
|
854 |
+ |
|
855 |
+ // Read length first |
|
856 |
+ err := binary.Read(stream, binaryOrder, &length) |
|
857 |
+ if err != nil { |
|
858 |
+ return 0, err |
|
859 |
+ } |
|
860 |
+ newset := New(uint(length)) |
|
861 |
+ |
|
862 |
+ if uint64(newset.length) != length { |
|
863 |
+ return 0, errors.New("unmarshalling error: type mismatch") |
|
864 |
+ } |
|
865 |
+ |
|
866 |
+ // Read remaining bytes as set |
|
867 |
+ err = binary.Read(stream, binaryOrder, newset.set) |
|
868 |
+ if err != nil { |
|
869 |
+ return 0, err |
|
870 |
+ } |
|
871 |
+ |
|
872 |
+ *b = *newset |
|
873 |
+ return int64(b.BinaryStorageSize()), nil |
|
874 |
+} |
|
875 |
+ |
|
876 |
+// MarshalBinary encodes a BitSet into a binary form and returns the result. |
|
877 |
+func (b *BitSet) MarshalBinary() ([]byte, error) { |
|
878 |
+ var buf bytes.Buffer |
|
879 |
+ writer := bufio.NewWriter(&buf) |
|
880 |
+ |
|
881 |
+ _, err := b.WriteTo(writer) |
|
882 |
+ if err != nil { |
|
883 |
+ return []byte{}, err |
|
884 |
+ } |
|
885 |
+ |
|
886 |
+ err = writer.Flush() |
|
887 |
+ |
|
888 |
+ return buf.Bytes(), err |
|
889 |
+} |
|
890 |
+ |
|
891 |
+// UnmarshalBinary decodes the binary form generated by MarshalBinary. |
|
892 |
+func (b *BitSet) UnmarshalBinary(data []byte) error { |
|
893 |
+ buf := bytes.NewReader(data) |
|
894 |
+ reader := bufio.NewReader(buf) |
|
895 |
+ |
|
896 |
+ _, err := b.ReadFrom(reader) |
|
897 |
+ |
|
898 |
+ return err |
|
899 |
+} |
|
900 |
+ |
|
901 |
+// MarshalJSON marshals a BitSet as a JSON structure |
|
902 |
+func (b *BitSet) MarshalJSON() ([]byte, error) { |
|
903 |
+ buffer := bytes.NewBuffer(make([]byte, 0, b.BinaryStorageSize())) |
|
904 |
+ _, err := b.WriteTo(buffer) |
|
905 |
+ if err != nil { |
|
906 |
+ return nil, err |
|
907 |
+ } |
|
908 |
+ |
|
909 |
+ // URLEncode all bytes |
|
910 |
+ return json.Marshal(base64Encoding.EncodeToString(buffer.Bytes())) |
|
911 |
+} |
|
912 |
+ |
|
913 |
+// UnmarshalJSON unmarshals a BitSet from JSON created using MarshalJSON |
|
914 |
+func (b *BitSet) UnmarshalJSON(data []byte) error { |
|
915 |
+ // Unmarshal as string |
|
916 |
+ var s string |
|
917 |
+ err := json.Unmarshal(data, &s) |
|
918 |
+ if err != nil { |
|
919 |
+ return err |
|
920 |
+ } |
|
921 |
+ |
|
922 |
+ // URLDecode string |
|
923 |
+ buf, err := base64Encoding.DecodeString(s) |
|
924 |
+ if err != nil { |
|
925 |
+ return err |
|
926 |
+ } |
|
927 |
+ |
|
928 |
+ _, err = b.ReadFrom(bytes.NewReader(buf)) |
|
929 |
+ return err |
|
930 |
+} |
0 | 3 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,53 @@ |
0 |
+package bitset |
|
1 |
+ |
|
2 |
+// bit population count, take from |
|
3 |
+// https://code.google.com/p/go/issues/detail?id=4988#c11 |
|
4 |
+// credit: https://code.google.com/u/arnehormann/ |
|
5 |
+func popcount(x uint64) (n uint64) { |
|
6 |
+ x -= (x >> 1) & 0x5555555555555555 |
|
7 |
+ x = (x>>2)&0x3333333333333333 + x&0x3333333333333333 |
|
8 |
+ x += x >> 4 |
|
9 |
+ x &= 0x0f0f0f0f0f0f0f0f |
|
10 |
+ x *= 0x0101010101010101 |
|
11 |
+ return x >> 56 |
|
12 |
+} |
|
13 |
+ |
|
14 |
+func popcntSliceGo(s []uint64) uint64 { |
|
15 |
+ cnt := uint64(0) |
|
16 |
+ for _, x := range s { |
|
17 |
+ cnt += popcount(x) |
|
18 |
+ } |
|
19 |
+ return cnt |
|
20 |
+} |
|
21 |
+ |
|
22 |
+func popcntMaskSliceGo(s, m []uint64) uint64 { |
|
23 |
+ cnt := uint64(0) |
|
24 |
+ for i := range s { |
|
25 |
+ cnt += popcount(s[i] &^ m[i]) |
|
26 |
+ } |
|
27 |
+ return cnt |
|
28 |
+} |
|
29 |
+ |
|
30 |
+func popcntAndSliceGo(s, m []uint64) uint64 { |
|
31 |
+ cnt := uint64(0) |
|
32 |
+ for i := range s { |
|
33 |
+ cnt += popcount(s[i] & m[i]) |
|
34 |
+ } |
|
35 |
+ return cnt |
|
36 |
+} |
|
37 |
+ |
|
38 |
+func popcntOrSliceGo(s, m []uint64) uint64 { |
|
39 |
+ cnt := uint64(0) |
|
40 |
+ for i := range s { |
|
41 |
+ cnt += popcount(s[i] | m[i]) |
|
42 |
+ } |
|
43 |
+ return cnt |
|
44 |
+} |
|
45 |
+ |
|
46 |
+func popcntXorSliceGo(s, m []uint64) uint64 { |
|
47 |
+ cnt := uint64(0) |
|
48 |
+ for i := range s { |
|
49 |
+ cnt += popcount(s[i] ^ m[i]) |
|
50 |
+ } |
|
51 |
+ return cnt |
|
52 |
+} |
0 | 53 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,45 @@ |
0 |
+// +build go1.9 |
|
1 |
+ |
|
2 |
+package bitset |
|
3 |
+ |
|
4 |
+import "math/bits" |
|
5 |
+ |
|
6 |
+func popcntSlice(s []uint64) uint64 { |
|
7 |
+ var cnt int |
|
8 |
+ for _, x := range s { |
|
9 |
+ cnt += bits.OnesCount64(x) |
|
10 |
+ } |
|
11 |
+ return uint64(cnt) |
|
12 |
+} |
|
13 |
+ |
|
14 |
+func popcntMaskSlice(s, m []uint64) uint64 { |
|
15 |
+ var cnt int |
|
16 |
+ for i := range s { |
|
17 |
+ cnt += bits.OnesCount64(s[i] &^ m[i]) |
|
18 |
+ } |
|
19 |
+ return uint64(cnt) |
|
20 |
+} |
|
21 |
+ |
|
22 |
+func popcntAndSlice(s, m []uint64) uint64 { |
|
23 |
+ var cnt int |
|
24 |
+ for i := range s { |
|
25 |
+ cnt += bits.OnesCount64(s[i] & m[i]) |
|
26 |
+ } |
|
27 |
+ return uint64(cnt) |
|
28 |
+} |
|
29 |
+ |
|
30 |
+func popcntOrSlice(s, m []uint64) uint64 { |
|
31 |
+ var cnt int |
|
32 |
+ for i := range s { |
|
33 |
+ cnt += bits.OnesCount64(s[i] | m[i]) |
|
34 |
+ } |
|
35 |
+ return uint64(cnt) |
|
36 |
+} |
|
37 |
+ |
|
38 |
+func popcntXorSlice(s, m []uint64) uint64 { |
|
39 |
+ var cnt int |
|
40 |
+ for i := range s { |
|
41 |
+ cnt += bits.OnesCount64(s[i] ^ m[i]) |
|
42 |
+ } |
|
43 |
+ return uint64(cnt) |
|
44 |
+} |
0 | 45 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,68 @@ |
0 |
+// +build !go1.9 |
|
1 |
+// +build amd64,!appengine |
|
2 |
+ |
|
3 |
+package bitset |
|
4 |
+ |
|
5 |
+// *** the following functions are defined in popcnt_amd64.s |
|
6 |
+ |
|
7 |
+//go:noescape |
|
8 |
+ |
|
9 |
+func hasAsm() bool |
|
10 |
+ |
|
11 |
+// useAsm is a flag used to select the GO or ASM implementation of the popcnt function |
|
12 |
+var useAsm = hasAsm() |
|
13 |
+ |
|
14 |
+//go:noescape |
|
15 |
+ |
|
16 |
+func popcntSliceAsm(s []uint64) uint64 |
|
17 |
+ |
|
18 |
+//go:noescape |
|
19 |
+ |
|
20 |
+func popcntMaskSliceAsm(s, m []uint64) uint64 |
|
21 |
+ |
|
22 |
+//go:noescape |
|
23 |
+ |
|
24 |
+func popcntAndSliceAsm(s, m []uint64) uint64 |
|
25 |
+ |
|
26 |
+//go:noescape |
|
27 |
+ |
|
28 |
+func popcntOrSliceAsm(s, m []uint64) uint64 |
|
29 |
+ |
|
30 |
+//go:noescape |
|
31 |
+ |
|
32 |
+func popcntXorSliceAsm(s, m []uint64) uint64 |
|
33 |
+ |
|
34 |
+func popcntSlice(s []uint64) uint64 { |
|
35 |
+ if useAsm { |
|
36 |
+ return popcntSliceAsm(s) |
|
37 |
+ } |
|
38 |
+ return popcntSliceGo(s) |
|
39 |
+} |
|
40 |
+ |
|
41 |
+func popcntMaskSlice(s, m []uint64) uint64 { |
|
42 |
+ if useAsm { |
|
43 |
+ return popcntMaskSliceAsm(s, m) |
|
44 |
+ } |
|
45 |
+ return popcntMaskSliceGo(s, m) |
|
46 |
+} |
|
47 |
+ |
|
48 |
+func popcntAndSlice(s, m []uint64) uint64 { |
|
49 |
+ if useAsm { |
|
50 |
+ return popcntAndSliceAsm(s, m) |
|
51 |
+ } |
|
52 |
+ return popcntAndSliceGo(s, m) |
|
53 |
+} |
|
54 |
+ |
|
55 |
+func popcntOrSlice(s, m []uint64) uint64 { |
|
56 |
+ if useAsm { |
|
57 |
+ return popcntOrSliceAsm(s, m) |
|
58 |
+ } |
|
59 |
+ return popcntOrSliceGo(s, m) |
|
60 |
+} |
|
61 |
+ |
|
62 |
+func popcntXorSlice(s, m []uint64) uint64 { |
|
63 |
+ if useAsm { |
|
64 |
+ return popcntXorSliceAsm(s, m) |
|
65 |
+ } |
|
66 |
+ return popcntXorSliceGo(s, m) |
|
67 |
+} |
0 | 68 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,104 @@ |
0 |
+// +build !go1.9 |
|
1 |
+// +build amd64,!appengine |
|
2 |
+ |
|
3 |
+TEXT ·hasAsm(SB),4,$0-1 |
|
4 |
+MOVQ $1, AX |
|
5 |
+CPUID |
|
6 |
+SHRQ $23, CX |
|
7 |
+ANDQ $1, CX |
|
8 |
+MOVB CX, ret+0(FP) |
|
9 |
+RET |
|
10 |
+ |
|
11 |
+#define POPCNTQ_DX_DX BYTE $0xf3; BYTE $0x48; BYTE $0x0f; BYTE $0xb8; BYTE $0xd2 |
|
12 |
+ |
|
13 |
+TEXT ·popcntSliceAsm(SB),4,$0-32 |
|
14 |
+XORQ AX, AX |
|
15 |
+MOVQ s+0(FP), SI |
|
16 |
+MOVQ s_len+8(FP), CX |
|
17 |
+TESTQ CX, CX |
|
18 |
+JZ popcntSliceEnd |
|
19 |
+popcntSliceLoop: |
|
20 |
+BYTE $0xf3; BYTE $0x48; BYTE $0x0f; BYTE $0xb8; BYTE $0x16 // POPCNTQ (SI), DX |
|
21 |
+ADDQ DX, AX |
|
22 |
+ADDQ $8, SI |
|
23 |
+LOOP popcntSliceLoop |
|
24 |
+popcntSliceEnd: |
|
25 |
+MOVQ AX, ret+24(FP) |
|
26 |
+RET |
|
27 |
+ |
|
28 |
+TEXT ·popcntMaskSliceAsm(SB),4,$0-56 |
|
29 |
+XORQ AX, AX |
|
30 |
+MOVQ s+0(FP), SI |
|
31 |
+MOVQ s_len+8(FP), CX |
|
32 |
+TESTQ CX, CX |
|
33 |
+JZ popcntMaskSliceEnd |
|
34 |
+MOVQ m+24(FP), DI |
|
35 |
+popcntMaskSliceLoop: |
|
36 |
+MOVQ (DI), DX |
|
37 |
+NOTQ DX |
|
38 |
+ANDQ (SI), DX |
|
39 |
+POPCNTQ_DX_DX |
|
40 |
+ADDQ DX, AX |
|
41 |
+ADDQ $8, SI |
|
42 |
+ADDQ $8, DI |
|
43 |
+LOOP popcntMaskSliceLoop |
|
44 |
+popcntMaskSliceEnd: |
|
45 |
+MOVQ AX, ret+48(FP) |
|
46 |
+RET |
|
47 |
+ |
|
48 |
+TEXT ·popcntAndSliceAsm(SB),4,$0-56 |
|
49 |
+XORQ AX, AX |
|
50 |
+MOVQ s+0(FP), SI |
|
51 |
+MOVQ s_len+8(FP), CX |
|
52 |
+TESTQ CX, CX |
|
53 |
+JZ popcntAndSliceEnd |
|
54 |
+MOVQ m+24(FP), DI |
|
55 |
+popcntAndSliceLoop: |
|
56 |
+MOVQ (DI), DX |
|
57 |
+ANDQ (SI), DX |
|
58 |
+POPCNTQ_DX_DX |
|
59 |
+ADDQ DX, AX |
|
60 |
+ADDQ $8, SI |
|
61 |
+ADDQ $8, DI |
|
62 |
+LOOP popcntAndSliceLoop |
|
63 |
+popcntAndSliceEnd: |
|
64 |
+MOVQ AX, ret+48(FP) |
|
65 |
+RET |
|
66 |
+ |
|
67 |
+TEXT ·popcntOrSliceAsm(SB),4,$0-56 |
|
68 |
+XORQ AX, AX |
|
69 |
+MOVQ s+0(FP), SI |
|
70 |
+MOVQ s_len+8(FP), CX |
|
71 |
+TESTQ CX, CX |
|
72 |
+JZ popcntOrSliceEnd |
|
73 |
+MOVQ m+24(FP), DI |
|
74 |
+popcntOrSliceLoop: |
|
75 |
+MOVQ (DI), DX |
|
76 |
+ORQ (SI), DX |
|
77 |
+POPCNTQ_DX_DX |
|
78 |
+ADDQ DX, AX |
|
79 |
+ADDQ $8, SI |
|
80 |
+ADDQ $8, DI |
|
81 |
+LOOP popcntOrSliceLoop |
|
82 |
+popcntOrSliceEnd: |
|
83 |
+MOVQ AX, ret+48(FP) |
|
84 |
+RET |
|
85 |
+ |
|
86 |
+TEXT ·popcntXorSliceAsm(SB),4,$0-56 |
|
87 |
+XORQ AX, AX |
|
88 |
+MOVQ s+0(FP), SI |
|
89 |
+MOVQ s_len+8(FP), CX |
|
90 |
+TESTQ CX, CX |
|
91 |
+JZ popcntXorSliceEnd |
|
92 |
+MOVQ m+24(FP), DI |
|
93 |
+popcntXorSliceLoop: |
|
94 |
+MOVQ (DI), DX |
|
95 |
+XORQ (SI), DX |
|
96 |
+POPCNTQ_DX_DX |
|
97 |
+ADDQ DX, AX |
|
98 |
+ADDQ $8, SI |
|
99 |
+ADDQ $8, DI |
|
100 |
+LOOP popcntXorSliceLoop |
|
101 |
+popcntXorSliceEnd: |
|
102 |
+MOVQ AX, ret+48(FP) |
|
103 |
+RET |
0 | 104 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,24 @@ |
0 |
+// +build !go1.9 |
|
1 |
+// +build !amd64 appengine |
|
2 |
+ |
|
3 |
+package bitset |
|
4 |
+ |
|
5 |
+func popcntSlice(s []uint64) uint64 { |
|
6 |
+ return popcntSliceGo(s) |
|
7 |
+} |
|
8 |
+ |
|
9 |
+func popcntMaskSlice(s, m []uint64) uint64 { |
|
10 |
+ return popcntMaskSliceGo(s, m) |
|
11 |
+} |
|
12 |
+ |
|
13 |
+func popcntAndSlice(s, m []uint64) uint64 { |
|
14 |
+ return popcntAndSliceGo(s, m) |
|
15 |
+} |
|
16 |
+ |
|
17 |
+func popcntOrSlice(s, m []uint64) uint64 { |
|
18 |
+ return popcntOrSliceGo(s, m) |
|
19 |
+} |
|
20 |
+ |
|
21 |
+func popcntXorSlice(s, m []uint64) uint64 { |
|
22 |
+ return popcntXorSliceGo(s, m) |
|
23 |
+} |
0 | 24 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,14 @@ |
0 |
+// +build !go1.9 |
|
1 |
+ |
|
2 |
+package bitset |
|
3 |
+ |
|
4 |
+var deBruijn = [...]byte{ |
|
5 |
+ 0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4, |
|
6 |
+ 62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5, |
|
7 |
+ 63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11, |
|
8 |
+ 54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6, |
|
9 |
+} |
|
10 |
+ |
|
11 |
+func trailingZeroes64(v uint64) uint { |
|
12 |
+ return uint(deBruijn[((v&-v)*0x03f79d71b4ca8b09)>>58]) |
|
13 |
+} |