File: features.go

package info (click to toggle)
snapd 2.49-1%2Bdeb11u2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 36,432 kB
  • sloc: ansic: 12,125; sh: 8,453; python: 2,163; makefile: 1,284; exp: 173; xml: 22
file content (192 lines) | stat: -rw-r--r-- 6,679 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2018 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package features

import (
	"fmt"
	"path/filepath"

	"github.com/snapcore/snapd/dirs"
	"github.com/snapcore/snapd/osutil"
)

// SnapdFeature is a named feature that may be on or off.
type SnapdFeature int

const (
	// Layouts controls availability of snap layouts.
	Layouts SnapdFeature = iota
	// ParallelInstances controls availability installing a snap multiple times.
	ParallelInstances
	// Hotplug controls availability of dynamically creating slots based on system hardware.
	Hotplug
	// SnapdSnap controls possibility of installing the snapd snap.
	SnapdSnap
	// PerUserMountNamespace controls the persistence of per-user mount namespaces.
	PerUserMountNamespace
	// RefreshAppAwareness controls refresh being aware of running applications.
	RefreshAppAwareness
	// ClassicPreservesXdgRuntimeDir controls $XDG_RUNTIME_DIR in snaps with classic confinement.
	ClassicPreservesXdgRuntimeDir
	// RobustMountNamespaceUpdates controls how snap-update-ns updates existing mount namespaces.
	RobustMountNamespaceUpdates
	// UserDaemons controls availability of user mode service support.
	UserDaemons
	// DbusActivation controls whether snaps daemons can be activated via D-Bus
	DbusActivation
	// HiddenSnapFolder moves ~/snap to ~/.snapdata.
	HiddenSnapFolder
	// CheckDiskSpaceRemove controls free disk space check on remove whenever automatic snapshot needs to be created.
	CheckDiskSpaceRemove
	// CheckDiskSpaceInstall controls free disk space check on snap install.
	CheckDiskSpaceInstall
	// CheckDiskSpaceRefresh controls free disk space check on snap refresh.
	CheckDiskSpaceRefresh

	// lastFeature is the final known feature, it is only used for testing.
	lastFeature
)

// KnownFeatures returns the list of all known features.
func KnownFeatures() []SnapdFeature {
	features := make([]SnapdFeature, int(lastFeature))
	for i := range features {
		features[i] = SnapdFeature(i)
	}
	return features
}

// featureNames maps feature constant to stable string representation.
// The constants here must be synchronized with cmd/libsnap-confine-private/feature.c
var featureNames = map[SnapdFeature]string{
	Layouts:               "layouts",
	ParallelInstances:     "parallel-instances",
	Hotplug:               "hotplug",
	SnapdSnap:             "snapd-snap",
	PerUserMountNamespace: "per-user-mount-namespace",
	RefreshAppAwareness:   "refresh-app-awareness",

	ClassicPreservesXdgRuntimeDir: "classic-preserves-xdg-runtime-dir",
	RobustMountNamespaceUpdates:   "robust-mount-namespace-updates",

	UserDaemons:    "user-daemons",
	DbusActivation: "dbus-activation",

	HiddenSnapFolder: "hidden-snap-folder",

	CheckDiskSpaceInstall: "check-disk-space-install",
	CheckDiskSpaceRefresh: "check-disk-space-refresh",
	CheckDiskSpaceRemove:  "check-disk-space-remove",
}

// featuresEnabledWhenUnset contains a set of features that are enabled when not explicitly configured.
var featuresEnabledWhenUnset = map[SnapdFeature]bool{
	Layouts:                       true,
	RobustMountNamespaceUpdates:   true,
	ClassicPreservesXdgRuntimeDir: true,
}

// featuresExported contains a set of features that are exported outside of snapd.
var featuresExported = map[SnapdFeature]bool{
	PerUserMountNamespace: true,
	RefreshAppAwareness:   true,
	ParallelInstances:     true,

	ClassicPreservesXdgRuntimeDir: true,
	RobustMountNamespaceUpdates:   true,
	HiddenSnapFolder:              true,
}

// String returns the name of a snapd feature.
// The function panics for bogus feature values.
func (f SnapdFeature) String() string {
	if name, ok := featureNames[f]; ok {
		return name
	}
	panic(fmt.Sprintf("unknown feature flag code %d", f))
}

// IsEnabledWhenUnset returns true if a feature is enabled when not set.
//
// A feature may be enabled or disabled with explicit state in snapd. If
// explicit state is absent the effective value is the implicit default
// computed by this function.
func (f SnapdFeature) IsEnabledWhenUnset() bool {
	return featuresEnabledWhenUnset[f]
}

// IsExported returns true if a feature is copied from snapd state to a feature file.
//
// Certain features are available outside of snapd internal state and visible as control
// files in a dedicated directory. Such features can be queried for, via IsEnabled, outside
// of snapd.
func (f SnapdFeature) IsExported() bool {
	return featuresExported[f]
}

// ControlFile returns the path of the file controlling the exported feature.
//
// Snapd considers the feature enabled if the file is present.
// The contents of the file are not important.
//
// The function panics for features that are not exported.
func (f SnapdFeature) ControlFile() string {
	if !f.IsExported() {
		panic(fmt.Sprintf("cannot compute the control file of feature %q because that feature is not exported", f))
	}
	return filepath.Join(dirs.FeaturesDir, f.String())
}

// ConfigOption returns the snap name and configuration option associated with this feature.
func (f SnapdFeature) ConfigOption() (snapName, confName string) {
	return "core", "experimental." + f.String()
}

// IsEnabled checks if a given exported snapd feature is enabled.
//
// The function panics for features that are not exported.
func (f SnapdFeature) IsEnabled() bool {
	if !f.IsExported() {
		panic(fmt.Sprintf("cannot check if feature %q is enabled because that feature is not exported", f))
	}
	return osutil.FileExists(f.ControlFile())
}

type confGetter interface {
	GetMaybe(snapName, key string, result interface{}) error
}

// Flag returns whether the given feature flag is enabled.
func Flag(tr confGetter, feature SnapdFeature) (bool, error) {
	var isEnabled interface{}
	snapName, confName := feature.ConfigOption()
	if err := tr.GetMaybe(snapName, confName, &isEnabled); err != nil {
		return false, err
	}
	switch isEnabled {
	case true, "true":
		return true, nil
	case false, "false":
		return false, nil
	case nil, "":
		return feature.IsEnabledWhenUnset(), nil
	}
	return false, fmt.Errorf("%s can only be set to 'true' or 'false', got %q", feature, isEnabled)
}