github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/features/features.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package features 21 22 import ( 23 "fmt" 24 "path/filepath" 25 26 "github.com/snapcore/snapd/dirs" 27 "github.com/snapcore/snapd/osutil" 28 ) 29 30 // SnapdFeature is a named feature that may be on or off. 31 type SnapdFeature int 32 33 const ( 34 // Layouts controls availability of snap layouts. 35 Layouts SnapdFeature = iota 36 // ParallelInstances controls availability installing a snap multiple times. 37 ParallelInstances 38 // Hotplug controls availability of dynamically creating slots based on system hardware. 39 Hotplug 40 // SnapdSnap controls possibility of installing the snapd snap. 41 SnapdSnap 42 // PerUserMountNamespace controls the persistence of per-user mount namespaces. 43 PerUserMountNamespace 44 // RefreshAppAwareness controls refresh being aware of running applications. 45 RefreshAppAwareness 46 // ClassicPreservesXdgRuntimeDir controls $XDG_RUNTIME_DIR in snaps with classic confinement. 47 ClassicPreservesXdgRuntimeDir 48 // RobustMountNamespaceUpdates controls how snap-update-ns updates existing mount namespaces. 49 RobustMountNamespaceUpdates 50 // UserDaemons controls availability of user mode service support. 51 UserDaemons 52 // DbusActivation controls whether snaps daemons can be activated via D-Bus 53 DbusActivation 54 // HiddenSnapFolder moves ~/snap to ~/.snapdata. 55 HiddenSnapFolder 56 // CheckDiskSpaceRemove controls free disk space check on remove whenever automatic snapshot needs to be created. 57 CheckDiskSpaceRemove 58 // CheckDiskSpaceInstall controls free disk space check on snap install. 59 CheckDiskSpaceInstall 60 // CheckDiskSpaceRefresh controls free disk space check on snap refresh. 61 CheckDiskSpaceRefresh 62 // GateAutoRefreshHook enables refresh control from snaps via gate-auto-refresh hook. 63 GateAutoRefreshHook 64 65 // QuotaGroups enable creating resource quota groups for snaps via the rest API and cli. 66 QuotaGroups 67 68 // lastFeature is the final known feature, it is only used for testing. 69 lastFeature 70 ) 71 72 // KnownFeatures returns the list of all known features. 73 func KnownFeatures() []SnapdFeature { 74 features := make([]SnapdFeature, int(lastFeature)) 75 for i := range features { 76 features[i] = SnapdFeature(i) 77 } 78 return features 79 } 80 81 // featureNames maps feature constant to stable string representation. 82 // The constants here must be synchronized with cmd/libsnap-confine-private/feature.c 83 var featureNames = map[SnapdFeature]string{ 84 Layouts: "layouts", 85 ParallelInstances: "parallel-instances", 86 Hotplug: "hotplug", 87 SnapdSnap: "snapd-snap", 88 PerUserMountNamespace: "per-user-mount-namespace", 89 RefreshAppAwareness: "refresh-app-awareness", 90 91 ClassicPreservesXdgRuntimeDir: "classic-preserves-xdg-runtime-dir", 92 RobustMountNamespaceUpdates: "robust-mount-namespace-updates", 93 94 UserDaemons: "user-daemons", 95 DbusActivation: "dbus-activation", 96 97 HiddenSnapFolder: "hidden-snap-folder", 98 99 CheckDiskSpaceInstall: "check-disk-space-install", 100 CheckDiskSpaceRefresh: "check-disk-space-refresh", 101 CheckDiskSpaceRemove: "check-disk-space-remove", 102 103 GateAutoRefreshHook: "gate-auto-refresh-hook", 104 105 QuotaGroups: "quota-groups", 106 } 107 108 // featuresEnabledWhenUnset contains a set of features that are enabled when not explicitly configured. 109 var featuresEnabledWhenUnset = map[SnapdFeature]bool{ 110 Layouts: true, 111 RobustMountNamespaceUpdates: true, 112 ClassicPreservesXdgRuntimeDir: true, 113 DbusActivation: true, 114 } 115 116 // featuresExported contains a set of features that are exported outside of snapd. 117 var featuresExported = map[SnapdFeature]bool{ 118 PerUserMountNamespace: true, 119 RefreshAppAwareness: true, 120 ParallelInstances: true, 121 122 ClassicPreservesXdgRuntimeDir: true, 123 RobustMountNamespaceUpdates: true, 124 HiddenSnapFolder: true, 125 } 126 127 // String returns the name of a snapd feature. 128 // The function panics for bogus feature values. 129 func (f SnapdFeature) String() string { 130 if name, ok := featureNames[f]; ok { 131 return name 132 } 133 panic(fmt.Sprintf("unknown feature flag code %d", f)) 134 } 135 136 // IsEnabledWhenUnset returns true if a feature is enabled when not set. 137 // 138 // A feature may be enabled or disabled with explicit state in snapd. If 139 // explicit state is absent the effective value is the implicit default 140 // computed by this function. 141 func (f SnapdFeature) IsEnabledWhenUnset() bool { 142 return featuresEnabledWhenUnset[f] 143 } 144 145 // IsExported returns true if a feature is copied from snapd state to a feature file. 146 // 147 // Certain features are available outside of snapd internal state and visible as control 148 // files in a dedicated directory. Such features can be queried for, via IsEnabled, outside 149 // of snapd. 150 func (f SnapdFeature) IsExported() bool { 151 return featuresExported[f] 152 } 153 154 // ControlFile returns the path of the file controlling the exported feature. 155 // 156 // Snapd considers the feature enabled if the file is present. 157 // The contents of the file are not important. 158 // 159 // The function panics for features that are not exported. 160 func (f SnapdFeature) ControlFile() string { 161 if !f.IsExported() { 162 panic(fmt.Sprintf("cannot compute the control file of feature %q because that feature is not exported", f)) 163 } 164 return filepath.Join(dirs.FeaturesDir, f.String()) 165 } 166 167 // ConfigOption returns the snap name and configuration option associated with this feature. 168 func (f SnapdFeature) ConfigOption() (snapName, confName string) { 169 return "core", "experimental." + f.String() 170 } 171 172 // IsEnabled checks if a given exported snapd feature is enabled. 173 // 174 // The function panics for features that are not exported. 175 func (f SnapdFeature) IsEnabled() bool { 176 if !f.IsExported() { 177 panic(fmt.Sprintf("cannot check if feature %q is enabled because that feature is not exported", f)) 178 } 179 return osutil.FileExists(f.ControlFile()) 180 } 181 182 type confGetter interface { 183 GetMaybe(snapName, key string, result interface{}) error 184 } 185 186 // Flag returns whether the given feature flag is enabled. 187 func Flag(tr confGetter, feature SnapdFeature) (bool, error) { 188 var isEnabled interface{} 189 snapName, confName := feature.ConfigOption() 190 if err := tr.GetMaybe(snapName, confName, &isEnabled); err != nil { 191 return false, err 192 } 193 switch isEnabled { 194 case true, "true": 195 return true, nil 196 case false, "false": 197 return false, nil 198 case nil, "": 199 return feature.IsEnabledWhenUnset(), nil 200 } 201 return false, fmt.Errorf("%s can only be set to 'true' or 'false', got %q", feature, isEnabled) 202 }