github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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  }