github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/builtin/bool_file.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 builtin
    21  
    22  import (
    23  	"fmt"
    24  	"path/filepath"
    25  	"regexp"
    26  
    27  	"github.com/snapcore/snapd/interfaces"
    28  	"github.com/snapcore/snapd/interfaces/apparmor"
    29  	"github.com/snapcore/snapd/snap"
    30  )
    31  
    32  const boolFileSummary = `allows access to specific file with bool semantics`
    33  
    34  const boolFileBaseDeclarationSlots = `
    35    bool-file:
    36      allow-installation:
    37        slot-snap-type:
    38          - core
    39          - gadget
    40      deny-auto-connection: true
    41  `
    42  
    43  // boolFileInterface is the type of all the bool-file interfaces.
    44  type boolFileInterface struct{}
    45  
    46  // String returns the same value as Name().
    47  func (iface *boolFileInterface) String() string {
    48  	return iface.Name()
    49  }
    50  
    51  // Name returns the name of the bool-file interface.
    52  func (iface *boolFileInterface) Name() string {
    53  	return "bool-file"
    54  }
    55  
    56  func (iface *boolFileInterface) StaticInfo() interfaces.StaticInfo {
    57  	return interfaces.StaticInfo{
    58  		Summary:              boolFileSummary,
    59  		BaseDeclarationSlots: boolFileBaseDeclarationSlots,
    60  	}
    61  }
    62  
    63  var boolFileGPIOValuePattern = regexp.MustCompile(
    64  	"^/sys/class/gpio/gpio[0-9]+/value$")
    65  var boolFileAllowedPathPatterns = []*regexp.Regexp{
    66  	// The brightness of standard LED class device
    67  	regexp.MustCompile("^/sys/class/leds/[^/]+/brightness$"),
    68  	// The value of standard exported GPIO
    69  	boolFileGPIOValuePattern,
    70  }
    71  
    72  // BeforePrepareSlot checks and possibly modifies a slot.
    73  // Valid "bool-file" slots must contain the attribute "path".
    74  func (iface *boolFileInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
    75  	path, ok := slot.Attrs["path"].(string)
    76  	if !ok || path == "" {
    77  		return fmt.Errorf("bool-file must contain the path attribute")
    78  	}
    79  	path = filepath.Clean(path)
    80  	for _, pattern := range boolFileAllowedPathPatterns {
    81  		if pattern.MatchString(path) {
    82  			return nil
    83  		}
    84  	}
    85  	return fmt.Errorf("bool-file can only point at LED brightness or GPIO value")
    86  }
    87  
    88  func (iface *boolFileInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error {
    89  	gpioSnippet := `
    90  /sys/class/gpio/export rw,
    91  /sys/class/gpio/unexport rw,
    92  /sys/class/gpio/gpio[0-9]+/direction rw,
    93  `
    94  
    95  	if iface.isGPIO(slot) {
    96  		spec.AddSnippet(gpioSnippet)
    97  	}
    98  	return nil
    99  }
   100  
   101  func (iface *boolFileInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   102  	// Allow write and lock on the file designated by the path.
   103  	// Dereference symbolic links to file path handed out to apparmor since
   104  	// sysfs is full of symlinks and apparmor requires uses real path for
   105  	// filtering.
   106  	path, err := iface.dereferencedPath(slot)
   107  	if err != nil {
   108  		return fmt.Errorf("cannot compute plug security snippet: %v", err)
   109  	}
   110  	spec.AddSnippet(fmt.Sprintf("%s rwk,", path))
   111  	return nil
   112  }
   113  
   114  func (iface *boolFileInterface) dereferencedPath(slot *interfaces.ConnectedSlot) (string, error) {
   115  	var path string
   116  	if err := slot.Attr("path", &path); err == nil {
   117  		path, err = evalSymlinks(path)
   118  		if err != nil {
   119  			return "", err
   120  		}
   121  		return filepath.Clean(path), nil
   122  	}
   123  	panic("slot is not sanitized")
   124  }
   125  
   126  // isGPIO checks if a given bool-file slot refers to a GPIO pin.
   127  func (iface *boolFileInterface) isGPIO(slot *snap.SlotInfo) bool {
   128  	var path string
   129  	if err := slot.Attr("path", &path); err == nil {
   130  		path = filepath.Clean(path)
   131  		return boolFileGPIOValuePattern.MatchString(path)
   132  	}
   133  	panic("slot is not sanitized")
   134  }
   135  
   136  // AutoConnect returns whether plug and slot should be implicitly
   137  // auto-connected assuming they will be an unambiguous connection
   138  // candidate and declaration-based checks allow.
   139  //
   140  // By default we allow what declarations allowed.
   141  func (iface *boolFileInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
   142  	return true
   143  }
   144  
   145  func init() {
   146  	registerIface(&boolFileInterface{})
   147  }